From c376c8b3f19664aa0aa4da4894a3f73de3e45654 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 6 Jul 2024 19:38:18 +0200 Subject: [PATCH 0001/1789] 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 5411d5b9e3..0233e1e422 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 4769c56165..278470b466 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 1681e3a6e0..21971571f3 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 1558436ad4..47256373d0 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 4ca9c86329..80f23291af 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 14fbfbc500..b0393de109 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 af601aa93b..c6c4934b44 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 b3db1c394c..3c13f9205b 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 239668a718..b11ac9324b 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 390bfa4b7e..d38dd2726e 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 f1a49d48f3..e86c7738fb 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/1789] 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 ac8f18be39..a16f09fd39 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 f33fc0ba5d..111d864480 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 5a207d0476..f9c3649cf9 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/1789] 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 b8b3219e5d..633b1824b4 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 04a18bb846..eccbd3eb1a 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 c664a131e7..d79870622a 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 ed161671c6..4ab99fc5d3 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 903ab9c5dc..212fdcf422 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 707ac02005..b88b3e0d31 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 561feac059..6907183966 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 d8df652c42..b209533fac 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 bd63830f59..67a98408a0 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 c662e1d9a0..49a3395d2e 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 32d0809bef..18f97ddc50 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 258a16b18b..6a9e539cf1 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 33f94341e6..59f5eba2ee 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 f378d912d9..d92f983a3e 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 c2a5a6c78a..c910b0cc3d 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 cae75b1609..0a50465169 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 6211e211c8..a3ea8273e6 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 13ee1ba1b6..8d8cf59016 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 23e34a9278..d1727f5824 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 dfc7761743..c196b25d99 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 fc8eec134c..c355cb5ad2 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 00cc1e7df8..b420ae29ce 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 bff07c1085..d72f73ed82 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 4ffacf46d4..67bcfa176c 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 95d4273ef8..529e1ae4a4 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 e87e8866b0..d4dc6fe133 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 9a3ddd2820..d778e89462 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 028600646c..fec1521768 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 04368960b0..d5c0197dd6 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 8c7a36287f..1d44c98f1f 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 6632716bd7..fca60cc869 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 e083daf91b..fbc602329e 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 dbd1418a1d..c48b2e439b 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 9b34346264..0b439191c7 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 d9ddf23f2f..42a85108c9 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 7423ac220d..0051383278 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 8b592f68fe..f409f9c062 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 d9cbee81c6..75f3d25628 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 416ce41108..d8cfceb646 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 27326f33a0..9789eefcbb 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 464e8cd776..0cb9f9ef98 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 4118909860..dfa0c1f17b 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 c2401d375c..ae35c9d74c 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 578a34cdc2..ca290a17c4 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 222d768b52..e9308cfb77 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 f7f46ca091..14c8d24824 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 20365299c0..bb9ae78f2e 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 26ceec4260..1de4b937f9 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 4115a752e0..06b9afceed 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 a14aba7112..3cd0ead0f9 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 eddf559900..60219c3b6d 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 6719de349c..4755296c6b 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 20e3087b00..ba1c0554fa 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 fee5eab88d..8db42d1f45 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 d35b68e30d..177b6b5843 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 23e99fe5a5..5451987023 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 d86a1bcb08..1276d663ff 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 2c490e3653..5d633de380 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 de2728001f..a313b60e2a 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 0180ab6610..a4b66596fa 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 61d9946c90..101bbfe2fd 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 38b36d25aa..5ba76a768a 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 158c285031..324dfcf37f 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 76d7bb5fe4..e6817382b6 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 65b80118b2..11586e3097 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 0f852d3210..675740d99a 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 85acf2d88c..af739bc02c 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 6ecfba9105..b4d73589a5 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 da6eb42962..5b88529382 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 f7c4424284..6ddf536bc1 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 30c59100ec..89e3c45ee7 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 d38aee7f23..10abe7d23d 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 608cf732f1..2f8a5a47b0 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 50e6e57b4b..b3085db0c6 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 545eeed111..b6dd1e2c2f 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 0af50dba47..8107aba19b 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 cfb2ddc559..9072bd8dd2 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 541dfd77e3..a6c66bbafb 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 03dc1aa8d0..21ffdd2650 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 6aa4b61bef..9a8ccb35cd 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 9f9f1edcfb..a46396f12c 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 b7562f4733..59131dcf01 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 add31fa48f..f761a5a47e 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 eb040735d0..fb3d949c4e 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 a6e6e2cf32..8ea1d5bb1e 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 520ce7add3..f953d40c49 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 75caa6420c..6236c3c63f 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 9473f35716..c9e4097d58 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 3cc415b3f4..70a7624453 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 e7a90c5bd2..613779b2e6 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 fcda6a015b..0906a3c84b 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 f17c3e08ec..5cbcf4425e 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 14a1d5202d..fc6056eccb 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 5e1a46273a..7f60d04bfc 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 99ed87ec33..e8782a5f59 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 9646518839..6e44a11217 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 0688b5d19e..57a80a5a2e 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 02714f2f59..5612e74ea1 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 aab53925f1..8827656c48 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 04bace7282..23ebd61ae0 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 b96096cff2..b420a0a9ad 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 da304b5893..19f8f6a22b 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 3d75abd627..ea5ce3970b 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 fcc0f51ad5..c7e98d3643 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 394eef707d..ef72841c5e 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 a60a6d205e..817482ef93 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 d205afafdb..c4a7769e28 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 64a37c96a4..a39cb4ef8a 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 c801f25642..de0b4d2b49 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 f90246ad15..f0f47ae021 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 57f3491010..38e9227624 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 4d562f1275..dbc36c155a 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 89d532512c..c9a0c52154 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 902ca005d6..90df53887b 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 a31a6eeadd..ac30175b35 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 f510a64c8d..29170f7fe9 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 e11f095fda..7c593c43dd 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 1c8b1e7bc6..31886c52bf 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 cb3bf3006a..5c8dad2f23 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 48270f714d..a32bc2eb6a 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 13ea681636..e377e42553 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 dd9258c64d..bd4f2466a1 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 600fede435..6053ca5828 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 07e92fbac5..bdd24bf291 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 15fb210b33..15f4ab8384 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 4f797d1af7..010b80cfab 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 b5e488ca32..7546993297 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 07a92a376f..ad461ddef5 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 2b19bafe56..9a703ed8e9 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 0ac6ef97a2..47e8481399 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 899b9153d4..34b91d99a5 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 0c15bfb8b9..f38caac26d 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 f992ad9ddf..9a15af910f 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 4696fb8b21..0eb55cbf5b 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 43910f8668..7136eb40d3 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 22e356fe70..5f1a719b48 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 9b1db71fc5..b2877e5993 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 3cf13bf0ba..5fa75939c5 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 cd93ab2247..8a429629fd 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 0873b4f802..5d336b845d 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 5803fd53df..63d5a2e41d 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 0fffa54dcf..72e00464b7 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 2ea5271d5d..9a7db5cbd8 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 26976a3dfb..c15b426f6e 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 6ca9db6ddd..34ab2c5a16 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 d88bcbb84c..7ae7634c96 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 06bca961f3..32fad2d253 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 26e27454ff..6fd0eaf8ef 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 940c21e965..8c7e405dc0 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 46f8933916..7f5a8dc369 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 2a40c35bb1..b3659d9536 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 dc2d44e505..cf56ee124a 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 1556984a90..5e5a07dc62 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 218d4597fc..7654b3ce1e 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 fb8f4dd191..b58cda6e15 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 854f584b47..eff28f5541 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 d040956fb2..bd4ac68d67 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 48a852c0a3..3279501f51 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 f4b0a340bb..7ca1a0a70b 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 30d08500fa..6938c898f1 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 55573e7eb8..6d508713e7 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 0c1523e60b..ca327164ee 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 8a824778bc..3d3ff248a2 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 007fa83218..d1872895dd 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 dee7d0019a..0b12946d8c 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 6ecfaeead8..e3f2cef221 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 e02620532b..3a8a2df77d 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 8d9659a68b..302dab88f6 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 a7c81ff9d4..d8aaea6218 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 6b8b2438c3..a344e4349f 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 0a20615ca8..7231c02e5f 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 8f7684e1ec..c02175b246 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 30129007d0..8f23935dd1 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 c6cbc86572..89f6c7f4bf 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 c0a0f52dd8..e9cf081b13 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 049ad46a6b..b3ed1cc12e 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 80412b4fa3..c388667cac 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 40301cc29f..4bc9c39ecc 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 2fd920f21a..43d3a19a09 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 ed7b33a124..6bb047212a 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 2250a8158b..b04990efdc 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 277d51c040..7f3408becb 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 942fa08d5c..6963d25670 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 52eb9a4b37..ee4292f65c 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 3e42e13b0c..ed6ddae657 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 4adc5be0e3..35e364f15c 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 55c81eef92..422affc9af 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 13a6596ced..355a4b9559 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 a87bdc516c..32c2adb50c 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 d43e0228ce..00e9e24fba 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 86df35e24c..0b065f8a25 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 7716f332b4..fb60b2b404 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 da1d8a09b7..f74db8e890 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 23310aaec0..1be1b7fab2 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 8882ebd0e3..21def5dbef 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 b39026a9c9..4e474c98b2 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 548c2ac422..550c00e41b 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 688c04499b..39b0dc509b 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 382431f70a..289fb6fe5f 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 fa8ab6b061..a59799f0aa 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 5c45df0ebc..fbe53cbc6a 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 48dd0e3705..ea9be27be6 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 bcbd3f0a93..e0bd824fad 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 bc8643af45..427ed83cae 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 544d6237de..628f0f777c 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 2928175be7..4b3bc54720 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 00ec3bcf8c..0dd31d9b2b 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 19c77316fb..c88997a3f9 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 aff7b5cefc..7e7f1240dd 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 3151d12ae3..0103f58059 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 38e9222a8c..6723ee9259 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 3763201b5f..6df730ddfc 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 953174fc70..78455184f0 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 27bc3ba5c4..1c24537453 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 9577dc7fa8..fec7a1c4f0 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 62a5d7ac1e..7537b8935d 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 99767fba7b..7a5da6f203 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 a4c6e675ef..19e823234b 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 fb05c30a50..7c3cfb163c 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 6689aa7a0d..c57a90f7a5 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 2857e853ba..695f59bf2a 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 65f8776c19..2ce90b2160 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 d95609b6ef..e5cf90566f 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 ac0569c509..574cabcf6e 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 77e42107f7..50667999a1 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 42935bd6c5..b37935a4ce 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 77394959b2..45ad9086f1 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 bf7f9eef75..a2730deb3d 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 b6e7a12ccb..0c62d0c7c4 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 addc7dc1ba..ad776bb175 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 24b04718fa..f8149dad21 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 225375678c..62bba62a2b 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 98db0e64ef..0f874eafbf 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 0a2c9aecff..773c36f6e4 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 df36796570..58d53a808e 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 de341a57d9..291ede59b4 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 f3582ba617..7196d50093 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 13910ec5a1..f9be2fd5c3 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 08818c1652..e0d523603f 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 4f1b190d35..9a4c1dd6bb 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 5edd559ed0..ce21f571fd 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 624eb251bf..5fa801ddec 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 5bc27ada08..455c341e4e 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 a38bab16fb..05df87b423 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 1013be73d4..d68d18220a 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 def6786f5e..83fae0a891 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 5626f20b1a..14c462c39f 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 eed9b93bf7..48722eeca5 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 11281ca7fd..b5863a4b80 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 efcf47d786..713c1502ef 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 a39226bbb2..e353d31af3 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 c5afa1e22f..3e3d1e112d 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 02c0c8b749..ef1e244ac4 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 870d1ff276..c578ef06fc 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 4719a4c8e7..9e3e86f187 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 aa2ca2657d..4af1282a78 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 af7761e625..3fc3a15666 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 da52332a1f..48121ba3dc 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 cd912d76bb..c1c038ca81 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 19c1f824d0..3bfcfe862c 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 4a041e369e..7c00dfe34d 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 c4a6f6ce43..fd75fa5306 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 0e909dd96a..98366e15ea 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 c11c26d7b0..6d52d72f95 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 d9d6203e5b..7c8129a3cc 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 0fe8fbb5b2..38ab53a124 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 4783a8f710..ed45d60e48 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 1113a10f89..c5d60ea909 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 d00085e985..c314c9c53c 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 27384f41b1..6eca6b0bab 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 d0628f435c..91ff9d9360 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 18717b3494..a3c3250326 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 ea798c2aab..e7bf3c553f 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 74ed32b7a2..e8a48922b4 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 cc9376b47a..bc82888d3f 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 9f46c124d2..793fd3d6d2 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 1326c4cbc9..21f4377ce6 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 2a97b73264..5df36d74bd 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 1dacb4c41d..ae601dabc2 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 c40018cb45..8bc982f292 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 8515df30b9..f21038c4e1 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 f2e42c14b3..a46bb9cdbf 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 a1e60d45a6..60861f7615 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 2a0f42303f..12702bce71 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 683b0cb259..ef5b130293 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 bbd5a2ca09..1e1dacc2fa 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 78707555c8..a14fa2c6ab 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 15c1ac94d9..220eefae95 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 35b613417a..5a360b8613 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 453eb5b250..63ec60d0b4 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 672cb81d43..0d93daeac8 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 6a8a409e56..18db804030 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 a41b9c631c..a525078b2e 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 91333363ed..2bdb4ff94f 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 cff6084b87..2fd49e7cfa 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 9d37c5b6ca..ae42c75d3b 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 d6278a50e7..fb75a7d94d 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 2597e771a8..591e916fee 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 55e3cbcf2e..31f975460a 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 cfd5b74e03..79445fdbe1 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 6fe9b9d125..e6506a4a21 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 fac7e5a9bb..cb86e0fec4 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 2860988c91..76fac3fa37 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 3ef4959ab8..3a12dcdefb 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 6adb97136c..988652d0d4 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 67166c39d1..8ec65fd9d2 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 f9c3649cf9..8632d6b59c 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 2acb010ad7..7070fc318b 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 969d978333..7506682426 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 7e8367df3c..3a6d194395 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 3d123ccfe2..9e687100f7 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 89e33adb3d..70eaadffbe 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 aa98107ae3..ac90178d49 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 0fa3440e29..bd6892cf88 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 557ac4848c..cc939049bc 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 b6fc6e471c..32fabbc757 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 a147d1872e..864f1fff21 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 c636424c2f..25f5bd3e84 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 7abfec64da..78fb07f24d 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 982a372d4a..4285d93bbd 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 59e26df126..bd857f7489 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 6e76066fee..84985abbce 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 0731d733b0..f2225f9b8f 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 1e20425451..604b1ad58d 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 3993684ddd..564950462f 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 fc8e931ffe..2b4f272646 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 3d5615b24f..9ea23cd6a9 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 04da7cd605..8f7533ba5f 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 f9a8c002c6..8b8aa99567 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 f2a338946a..992d53d13a 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 91b807b3ad..79a3c96761 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 6a32d4eacd..5cde43a155 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 fd69ce8129..f54fc37ba4 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 adf7905bfc..8fe817e91d 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 7c9a569dcc..4c7410e2f0 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 6ba9c4d28a..2dd864952a 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 7bc10879f4..9940b28505 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 cd4b526a77..45b65f43a6 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 4fc7daca3d..351d90b467 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 018d8593fa..7391715313 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 2f2ef828ec..330c77562c 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 b3cdde365f..4c98f8d596 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 806c24c815..d26ea0c4be 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 b72a011960..90ef29825e 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 b538f44b3f..234ccbf95b 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 3c5947ac5c..b6023cae16 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 2efd1c1a97..0b047b08b5 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 edbb692931..09e9968a1a 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 5b8504a7d6..58b63fe1c5 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 c6e2f890a8..4289056bcf 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 3eee9061b6..9c510755ff 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 cf1a6ff76b..c92c6c5b74 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 230ba5902d..e57d3c3f4f 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 49c7778cd6..48051aafc1 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 4d62565c1f..2e64aee94c 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 745a8cae15..15fc4d8ad8 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 484690a8b7..68ccf90ed9 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 b891da3894..8e6d32054f 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 ff52194908..870f0b7c66 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 3112fefa48..7c60edd6cc 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 7f92bfdb5e..bdfce9f04c 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 3a81fbb4da..3e303b82b9 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 d0b71b91a7..683a341f2e 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 33f536fe49..5cbdbd2907 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 d4741bc1b6..5a6bbd4a04 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 bd3c9b54ed..abeb693630 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 262b601bea..c7d8cc141c 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 3ba005da82..04f4fbe8e7 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 0665eee656..b66c18b805 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 e3f7927dec..96cdfca07d 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 5d76711ce4..a9ea2848c9 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 2f10d431c5..ba559afac7 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 f3ff0aa2ab..d99bbf8955 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 d437c86f5e..ed04458668 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 7e858b714e..a05f550105 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 0fba6c34ce..ce6306bada 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 9dd5a25958..61d1852c7d 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 0e6ce5ae77..26cb1be007 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 00fc7e3e8e..63d7eff0d8 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 9668d523be..dbb69f8e9f 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 15a814b20a..1017888c59 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 73924b6081..3db627179e 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 3d766f5fee..ad10221352 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 a5aaa8443f..be769e05a0 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 909c357874..363109b7c0 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 e1c7e31e1f..2fccc5b3c4 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 ff3e666cff..7ee5729483 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 9e4b1957a0..240d3d3bc5 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 b4059b7623..9d6cacd951 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 7c9501cbdd..06441ef778 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 9e897084cb..2ae0d7c9ff 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 bb772064c7..90ad0dd185 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 920c84e7d4..f439f811e9 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 ecaa6d3109..600405d363 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 4714738167..c84670be53 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 6d72ef75bc..c9f0d94ac6 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 31433330b8..0f4cd81a03 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 7888b26abd..3e111949cb 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 8473684acc..07886541f8 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 66ca12c487..7f58e257fe 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 7c850a582e..4b980e21d7 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 fc9316c300..7649825119 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 d114609b35..7bfdf37f9b 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 b74f447ffa..0652b11383 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 2e318eefeb..4aa6d510da 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 3f663f7909..51a89fc329 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 a6f23841f3..5fd19b3105 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 5140d7296b..38b4bae4e9 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 bf4421d1f7..607a08a37f 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 e9a8b8a161..6582c358b7 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 fb9a8ecdd3..8698e6196c 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 e93cc59e67..e81c0b22dd 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 5cda2321f1..fe3e09bd4e 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 fdfea4ebf9..ac2f0b9bcd 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 65ca5bd674..2982f71a62 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 d2587839c3..f23f2f1234 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 9f0e62bc0c..627a1e5b80 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 fe47fa1452..f3f2d38965 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 c565601261..2a953cf36b 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 528d2daf8b..1503fe6f07 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 72aad19740..cf908f004b 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 521279beca..d1615303b5 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 5f2c4d531a..7882c3473e 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 fd4a3c6e20..1ae24aa0ca 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 bea7e29b68..80079c5495 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 fc5d8ca505..048aa82a1b 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 52472fb47e..8c733903c4 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 021353bbd0..8fe066e60b 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 6d48c393c0..8c1b53457a 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 33da77eb36..a0074cbd4a 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 05c48497d0..ad5e08981a 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 0907413ca7..a1f27a33e2 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 cc449b8f30..8547f7b2ed 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 8b8fbf6722..229b681818 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 3e5dc7524c..f358c5070a 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 6a2cc62542..0bc7c1f4c4 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 96409f9aa1..dee1dc6c30 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 cf35062faf..3e91fe8dbe 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 332d192698..ab83f1d019 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 21092c2100..4be69c0ac0 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 a9b64b441f..61d2085fec 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 a1eaf7e131..46b5e712e1 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 94cb7e49b0..0bfcebf956 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 1242f85cfd..e74b192a67 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 61c4fd342c..5b5f3545a9 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 c455cd8d21..fd6351a003 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 6ef401c577..7b329d01a4 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 547e2f4fe4..3cb1b93328 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 ff66d9bb9f..5ab745647b 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 0bafe975d0..c6c0f92b14 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 0368895760..45f7b490b1 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 ce70e82425..8d83452357 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 10a3a67758..bc515b9d04 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 916c6e1c7d..cd549f67e3 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 d8033f5394..697822b55f 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 ceb074e36c..4e3bddad6e 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 773aabe5d5..5cf96d8ad2 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 3b8ad5f97d..a8f525c91e 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 be527ffad0..9d0bdbf724 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 f7cdfdaab3..b58a589074 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 c86fede587..c7658440ab 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 c168b74ce7..80d3af0f77 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 355fa61e01..cf39baa35c 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 438398a4f9..551f56c464 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 164e63d9d7..5e58d49b3a 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 21d6bda749..319379ab2a 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 d8256191a0..71597d271a 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 f8425ff295..71636b8ca0 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 ae2658de79..bf8ac7f05d 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 b5e122eb0e..047ce4cee9 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 742dbd1842..e9aebc1c26 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 b0a6aae26d..d1863e1945 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 7eef8ba523..5e40800631 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 e47d9cf5d6..69ecc87fb5 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 5aeb3fde46..e0ff7c27ba 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 413f32fcd8..aff2164a91 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 e25a2ac974..5fc694a46b 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 c84092f810..cf4df676e9 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 d36c15a33a..019d6a2979 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 7e31e5adcd..e90aee6b80 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 5376f6b17e..22434786a8 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 78940cac12..a8d1b1e476 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 86697971b8..7fb999403c 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 406108db1b..241cef7c6c 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 ce397c3e21..07e39383c7 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 b675469bac..5540646616 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 c6318d07a4..24f6850492 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 21755c623c..816e67e7b4 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 34a35ab11f..036cf3aa85 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 32c9361d65..3b1fdca948 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 4d69f3a7ba..c73a1f8340 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 2850e2bafe..e42a60d5f7 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 44c574fb68..8d680e9306 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 d41cb743ca..f107804843 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 831829355c..02f3db6590 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 60da2b56ff..9271ef2782 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 5222647d17..477210064a 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 6074f8d20c..3777b5d6e5 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 d1bf93a6db..80444eb335 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 8bf57ebcd4..f0eb0e338c 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 55423dbf50..a52cc8d718 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 828cfa1b82..39a4da06bd 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 9fc4e4067d..5387a18bfc 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 5d35d1be1a..9ac004779d 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 250b55bd6e..971868f1c4 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 d7d318322e..81cfb1f471 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 081cdef1e0..b208766609 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 66f447db3b..6780fb91d3 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 9513924e6b..adbe33ac83 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 4918a320d6..7b3682f213 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 32ef874c5f..0ce221250b 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 80a473b713..ac87e2b8a1 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 01ae378030..b6eaa9d790 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 42a885b6ad..68ac27fbf2 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 27003ecade..d0e52ead39 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 ce1295ffc4..40588a1f56 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 d6b891f465..58b13d04e3 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 895fb96360..e91391e8bb 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 5cfb38f074..2de89b7f65 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 ce2ea802d8..ad16fe919b 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 555cbfc0dd..ca1a822f99 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 37b72e1c72..afdbf2c5e7 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 b8cd1684dd..3e62e4b68b 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 22ce47032f..2e246941b7 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 ab14e74d36..aa0bf2ec0b 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 1635b12050..3b123b3e4d 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 5c4d597cd9..443b7dcc10 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 c64b29f8bf..83d7842813 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 dae162500a..40839309e5 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 528e5c76e6..6dafe5d35b 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 b6bdb3ca34..f07b2aac37 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 34855261ef..ff0d6eb8e7 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 952da73ba2..85c126a00b 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 256d2bf781..5869714cf8 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 e2fabe49ca..f76f39c616 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 d934b57a6d..495cdf0248 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 f1b226ad57..17f166b788 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 ccb160f60f..2e860a391f 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 aa868808d4..52ec29d01a 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 6af054659d..f3373d4c10 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 db1020b18a..f7d2a6dcb4 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 e0899850c3..3f8e6a62ee 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 7ee53824ee..0dfb5d71ec 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 8d584b72f6..c7e0113764 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 267d8c8200..d7d3b5bc71 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 1231a0d5e2..1c826e424c 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 cdb62d2597..fd6866bc72 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 e2ee44c466..f3ed6e08f8 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 2aae5d488c..c564711b2f 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 508214f275..0756fcac6b 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 d91bf2fc14..499b62e154 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 bcf73954a9..f4a6e17499 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 85f648ab97..71087449d7 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 d9b5655ff6..5d3c7c2576 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 f07d6dc5c0..a2ead680c7 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 59c0bd8448..327b55c202 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 e7f7f9849e..6688dec466 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 c9a5f231b6..55c69ee3a5 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 2a15d3139f..830d0f9a62 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 be17a4c76c..4e89ed8ec5 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 4c34fb4f43..1925232132 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 d2bdef50ba..fbedaffbd7 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 82bb5d529b..be2eb86b1a 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 177d1d218d..7eeb304c3d 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 bb62442ec8..cf058b54a8 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 1b4e1ddf03..b849f968aa 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 371f2fa61a..cc7d673620 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 aa0a9436de..b9b489c12a 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 e5c0d7dc8c..c21790b738 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 01b80d7f68..24dc76e186 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 685fa41de0..2df15b78f0 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 72fca5073c..a97e2caddb 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 040a5aecce..415e04b748 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 170d9ad05e..841ae2f46c 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 98f72deb73..218edf5734 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 5aac3ee5c2..83886f1ea2 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 06d92643ae..6cf2b6c5c1 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 a2107c73a0..0b29af004c 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 2c6dbd3ad0..0c5acfbd07 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 6a9586a820..5df0e84af3 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 2ccf122ac2..153c222091 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 fdecd2073f..827cf6e876 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 b890a8ff66..93ade0dafc 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 df5136a808..724a9ab079 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 585b00137a..7d5967fb2b 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 3cd1c51390..ab3565a612 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 a142269a68..7a64f9e7eb 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 24118f5fbb..04694b4447 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 d722abffe1..1a823e18fe 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 ee9e8f257c..02006c1619 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 b6b26da76e..d76428ff5d 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 d06cdc8f4a..07adcf6b36 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 a1d8e52f35..a80b44b995 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 cc9da2c3ed..ecca6e7d09 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 f364c82b9d..6e056c6df3 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 84f2c81f3c..f90bdca70f 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 ab9897713b..4a02e64989 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 dc1d4b63cf..02cb886886 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 0caa2c2ae9..1494208b5b 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 b5af3ebd1c..d453fdca38 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 2d7c048d80..f346ec8e72 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 deb3f8212e..89a5d1fff1 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 9be06b937d..25ddeef8bd 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 3de12e41a3..d97c3eb76b 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 0359073c44..9c1efe6c4a 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 a21d0561e5..6c21c3a33d 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 e35854b5ec..3e9c477cb3 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 8c786d7359..2cb7788d59 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 9c149811bf..6f5b049c7b 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 e95b2e7712..e73c28da76 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 3700dd3b73..2fe0ab6bfb 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 f78cbaf01c..fd60ba01a4 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 9df0d06b44..46901218ef 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 465c2b9f36..c26dd6f290 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 e808174a9f..30be451eae 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 4f99378be0..4590950baa 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 6b60d6557a..eafb0e946d 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 80f21c3de7..fa9a6ecb06 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 b62c494ba5..4ddff55f11 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 234c4e8a71..5eb8d8bdf4 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 c90b398fcf..b08f12f32d 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 5ad6141c06..72914da2e7 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 7447c13cce..ca13ca07a6 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 330ac5a8d1..e44f5de4c9 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 a660be4e61..1a07cb30c7 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 d97e3fd119..d8e8b00fcb 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 3878c1c77c..b0b364030a 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 a9a1a728ec..f1b9181923 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 d6d7b603c8..04ca707933 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 060bb2a27a..d087a19ce1 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 388dcb3208..67eee30157 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 0a37e48657..d381cdc659 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 b1f2f013c2..1706bdb76e 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 da361f018a..00a2352c99 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 abeaff4110..ed78c46f17 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 9db2493f65..8f1828cbe4 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 e32d6fa551..ab8553b5cc 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 c93634b94e..0f1388314d 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 b51cf7763a..3012ef21f2 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 524f509395..702fc0f299 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 b0934bb0c4..b205234dc2 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 3305529134..1dba6ed6d2 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 6e839f54ca..fa747d6a2b 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 92aa955a0e..409b32d65f 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 cbd397ee94..6fca3226a5 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 ae220df96e..fefc7bac89 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 57d0c165e6..165f41741c 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 15027c8c2c..2d18943608 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 357d3cf9b3..2f21d52123 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 f7a1a8120b..1e9d6b1ba2 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 2bb1fb915d..28dbd1e368 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 a169d86794..4f7df1a538 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 e7b6d2c3bb..ec6d40f0a3 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 008f4e7d08..e950e4cb9a 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 17c4f09990..9d414f6f2b 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 d1140961a5..00753a620c 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 d50f8dfd32..6eca4510d9 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 a9bae8d9d5..815fdce793 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 71102e4f23..a1f16bbd07 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 ae407a2a30..d6c55e62ec 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 b961e235fc..68d8a95465 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 92415f012c..381a2e1de6 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 fd17b1f850..b8ff7713bf 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 4f6e564849..7b24c91c52 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 2c42f7be1e..517c41b909 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 43b8760503..8dc06429e5 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 9cd551cbf3..6f3382d829 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 099c1d6507..9ea9c07342 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 e34f6fba22..2d4dea0dbd 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 e7d3f8e73b..40551504fa 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 7ad14e65b6..eee8ad3281 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 0995b7574a..481d99f165 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 5610c04925..56ed3c3cf7 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 1fdb7a17c0..3e849cd1dc 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 3b89aea5c3..0008644c1a 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 e37db8ae89..131ee1ec91 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 4713a0cd08..184821b5a3 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 22be14246c..46164fff02 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 5d4f65420c..923d65e143 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 6734c54e4c..fe40a1bd61 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 029f558bb8..4a227ffd07 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 1efb15ffaa..087c89b6ed 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 5c24f9bca8..d8be08408f 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 6925576665..56746b7f7b 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 d1d4b52e8b..b008e63470 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 27d8b3d6a3..9540555e4f 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 174e7c9385..de4be753d9 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 03e9422e2a..9a33044401 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 2c18b2e3ef..076ae342a0 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 8b53af21d5..25b485dfad 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 3968f05792..2ce5e70cce 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 d01b17396b..0070554896 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 031edf1743..9e4fc3ebf1 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 94171408dd..84f6db5239 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 443a31112f..d07066b4cc 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 9da99b88e9..520b426424 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 2042177a2c..c8d0646083 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 7efa804442..c9ba6e0a24 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 d68fca010a..548e176c1d 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 e5d3d08b4e..5d7a9abcc4 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 0a8d6cdbcf..773c715d04 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 21d6ff3e1f..f9a6e61602 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 9d929c39a2..67a03643f9 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 8fd67c1e5f..3076d2d3d8 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 6ce75189d7..0130c2f633 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 2b138d5bbd..62678e3f14 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 0132ac0d3b..57577364cd 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 2a8ed41bd8..4c4a2aa655 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 3f61fd0fb8..f42cb7e7d5 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 72b30052e6..bbb53f1089 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 54d64878ad..de05aaaab9 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 1195f47022..6b72cf6df7 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 ca10cd4434..be6a9fca4f 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 35ce4c2c8c..d9f62fa618 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 b0d9cf2812..375eb439cf 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 e45011b361..8588a1a57a 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 3f6cc8b000..6cd33e10d5 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 ce0b1d1f05..981415e915 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 9d72bb3895..c71ce58bbc 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 a024d70b14..93e7a0fb4d 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 b50e887e3b..30f7233614 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 47d132b639..2b5c7d010a 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 a622964ed5..5e777ae164 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 5e326c12a8..2d2ab20f6a 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 9e15b0b33f..bb2c2e83c0 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 a2995c3886..525a9ecbb6 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 5acc86ba91..bfe8b1f7bf 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 4e294dc0fe..e70d2eb292 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 838e9bc67b..2355503a93 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 a023720118..30685b4d6e 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 d99fe4e4ec..b19c3904bd 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 873209c00f..4ffabf4ccd 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 a838200800..bf673c0fe8 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 ef7771dea3..c7a2338b30 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 cd6a40afe3..ff16ad5339 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 a88780c212..6c125de9b7 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 0fb7b8bc41..a0b8945f52 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 35b081c092..a0b9b85c84 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 22ddff6f25..a4f81cce53 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 50d8bdb997..be6bc0923a 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 3024d5fdf6..ac0b980e01 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 ee74c1f0ac..5d602a2fe5 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 7b5f1af9e5..f6d020a9e2 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 2492802799..80b8bd5fb2 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 6d6fb6b7a9..ddee015752 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 2c8995a0b3..3a05d8d3c3 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 fedd8de5d9..4bae22b6a5 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 d64be7de95..a24ea70b44 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 dfb94ecc15..0c2dea58a7 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 3ecb8d6233..a606a29b87 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 956f7fe0f0..b952f9a3c5 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 827f6a8724..8cdf151a33 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 3732da9802..d7a3a4388b 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 3a6f7eb2d3..91516979e0 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 911a7a05fe..4dcb3e0bae 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 1c5c6001e8..429a1ed0ef 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 e592c0d98e..6910c787fd 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 68f71ae5c7..85a6c85960 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 ce5c8fffcc..17ab507d2a 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 6402df1887..d9e7665e9b 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 0f37cede7c..692da9a71a 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 65868071a8..91c52036bb 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 fe0d25a3f5..a92ded0e4f 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 1a50b299fc..7b74800753 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 528a20c731..7cc55fdeb1 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 9992c86c9d..a251840dd9 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 d77f882c50..aceabd9f78 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 3a1158c176..866bbc3ddf 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 6b8aa73a5b..81b0015029 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 f8205fd24f..0dbad8299b 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 a008143cd2..96d93510c9 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 cd8418f5b2..1e11f5e641 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 4eb281839d..3bf4a22ccf 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 4fabd64eac..22c77fc545 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 837c62602d..a7659febe1 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 a861ab2f51..723a0aa79b 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 919587a216..1c88f9fbc2 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 095f49475d..fc2593bbfa 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 08a214f997..b2218268c3 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 75cd512c3e..7f5e130f09 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 af9559cfaa..203696b2fd 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 652b0f1922..d78d2813e3 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 8cdaeaa36d..fd81121a2d 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 3195db7454..d249eef75e 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 09edd26a3d..e0e8547a32 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 3c1fcf4d23..3a07eea396 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 1412335a8a..fe96c09839 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 ceb2141456..9570715ebe 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 a0d13d45de..5af535902a 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 386b844a1b..44028fe427 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 3b207cf2ad..e69b4058a6 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 e93934d20b..c8454faf62 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 a0fc78df5a..8c11c1816f 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 88b7282eb2..8c5b9c5a64 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 0fbb2a635b..68b993db00 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 da07902233..da13e04d88 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 b26457a1e1..60c3af565f 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 c5a08ba5ab..80ef4bccdc 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 201408f8f5..e33db8f0da 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 2b5c809341..74c5328ba4 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 77879f99a3..bf49b02765 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 8056f39420..6c62fea688 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 9451787f4d..4f4aedec6d 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 e715ce7d91..b404ff3464 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 5e62501f72..0c74e65a02 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 097812f918..ceeb071238 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 73612f3074..4dd23f32bd 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 6948568540..177af08c6e 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 3ae9b74c46..6abc321550 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 7ce2cee84d..5ccb33a736 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 1bd3f82cba..cd97f3b9b4 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 88442c2bde..ba53111760 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 8da2427d31..b85150e267 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 fbe15dcfbc..fc665ed901 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 ca588bd711..12d3fadf59 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 f5c59363ff..69ed263479 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 ef289640e3..563bec59f7 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 9c24597525..0d4487d217 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 648781ccc5..177079ac6c 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 7678406a9e..03a80e73bf 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 43efa6646c..dc75eb024e 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 89aca44aa9..da72e99ebe 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 7f3bcbdcfe..d234baa6b1 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 fccedd9ec1..a4921c9201 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 df4e9f5218..7e387901e0 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 e6e5df0f9b..4cfecb05f6 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 eb58de74bb..29f416d3aa 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 ad4bbfa25e..d0502cb9a1 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 07a395a5fe..a0e82946ff 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 e5bd1caf7b..01e735d949 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 8547fa39a9..525b65caed 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 663f1c1513..02e1115e1d 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 2a1f4cd4f7..48cc18025b 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 b64fe8f46d..f7fa61c09e 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 ea3c27a13c..002a87f66e 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 75c0d0b5ec..1d9dc436c0 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 307be3446f..2ba6b7ae90 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 0cce10ed32..d69e030e2d 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 414ffc12aa..deeb9b6ddd 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 166464a884..a38d656556 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 f807e3d65e..00fbc34a1d 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 5ad7793665..1ec43f153a 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 260abd3d93..0be67d5e08 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 e40eab90d3..8cc0e54e5e 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 a3ab7a04bd..786c61302c 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 55d3a18aa3..bcbb23ea42 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 217103bd09..c311cde2f8 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 4f13c5eec9..8c3f7d2da5 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 d270e691f2..0b0edeee19 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 a79f924417..e7132ecfb3 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 654d7a1ea8..809ba5bd20 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 7299a08100..e616fffc0e 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 c4ab9e8b9c..b2508600f4 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 a7edf8777c..7bca91ccd9 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 62e1d8435b..3ca26e156e 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 3ca47235f8..9871a469c9 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 2e85f59099..33ff1509e0 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 785991b2c1..b8c7fdfe97 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 d697380439..d6f5a8251c 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 1beb76de40..0d6342e584 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 10335fa3e8..e7436d8275 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 16e343e12d..fe418f4daa 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 d2d5ed8360..d255aa8c15 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 06fcfb7c74..bd9fdd6f0a 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 c1684afa9d..a13714293c 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 2139222a9a..74a2903ea5 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 af0f49d231..24561b5598 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 5bde2a454e..5b177a9f68 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 dff732d028..0f33b7f532 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 cd12f678d1..4e49cd465f 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 64935edaea..540dda82bb 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 315acd8488..1d390b59d0 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 80bd5866ec..970e2cb39f 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 94584e3583..e68f0338f7 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 ef16c66fbf..1a693eff8d 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 8571c82ed3..4b974bbe1d 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 47552e875e..cb6c35bbdd 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 811727ae39..156a8c1a46 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 45cff582a2..bf4c3abc6b 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 4573e7d89d..2378a291b3 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 413a163535..d1458a27dd 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 22bbbc2047..2cf4226047 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 8f4103018d..c80bb0950b 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 bb1ef07430..6e38a43a25 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 c10cbe2e58..2651cb6b90 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 9367e20b1b..8cf3d88c19 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 754ac737c8..17e293973b 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 1d0e07a500..719fd172d3 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 73c34fa9ed..9e495b3163 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 ed2bd20acd..45ff96e2c9 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 4c634ad0d1..435d4b067d 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 1e1075c812..83cfb69ef5 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 7f056f90ec..edcbbf7b7f 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 d4c995f933..9368cb4279 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 5940c2d803..03d938a7e4 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 112ed30292..7e70566044 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 e9564278fe..d34d79059a 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 bae299abaa..1a404526f3 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 f028fbea7b..34854d28cc 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 41432d1c3a..32113eb312 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 d6cadad384..5e15482336 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 cd7f63662c..04c356151d 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 1fdcbf4937..563ade3589 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 a492e79388..e20c503c05 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 7b691b6257..6bde75bc6d 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 bd00c0f12a..9e3e5c892d 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 b18c8534df..ef42505ede 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 39a327157a..6ab34811e5 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 b0eee10181..e1b35ac7ee 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 5f996c55e1..81a836f620 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 5f37995240..880a3c8c21 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 0a96b8d73f..5de459fb73 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 b69b412e58..9927cc252e 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 0dd934cd32..1a04c7d5b9 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 caaca73501..2eaa4308b4 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 438d6440e9..26c2773555 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 de7e8dfe40..51bc6c4b2d 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 c570e8ec86..612c66d786 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 a6c2fdfdb5..bd1f73b5c2 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 936f5b5adf..d5678343b5 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 3dc2dbba5b..b03a68655d 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 eb396ad797..8b6a6133cf 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 566faa3243..875529ae47 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 47dc50fee9..fdbf783378 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 0daed61c52..bb29690bd6 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 a8d9c45276..216423696d 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 4e4de99bc4..15dc36a481 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 aab1448733..bab7bdcdeb 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 0eb26199df..c4000b9aff 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 d05b999422..20ca925c1c 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 38bb1f3044..472683ffb1 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 470d196894..c523fde243 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 3864db74e5..2d52ee99e1 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 a06e2b3370..eda7d4df47 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 29eaf4f290..68e7855bb7 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 964d605b27..b8b5b38c6f 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 ae3aa752f9..dd46ed4c2a 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 13f8d238b0..d6cb144506 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 cb246f7e3d..fe28eb34ca 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 f5d0055d9b..a782db6686 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 08a366a59b..bdff31b842 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 422e1355c6..f0cc1561fb 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 e1633c0b64..20128f0a92 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 cb7cf0eb07..083914085a 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 11a5f74752..60fac48358 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 ea15dae2e8..6be372d38f 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 3e158d1bcc..acd5ab309b 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 6300d6887d..d898085584 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 8907e48a66..70d08b9098 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 e7e17b90f6..3e0873ee11 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 8a67f1d6ab..16c7a37788 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 bbe3c9de62..487857213d 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 842821b1e2..fb3e577d38 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 56478c55c9..83df24904f 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 bb11536c1b..621831d168 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 e6aa5823f5..c0b0df2cf5 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 098b3f1567..b988a7883a 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 e5ddac62e2..b89989cb51 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 1f3fd26d4c..71bf2d7ead 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 1da17b48d8..f6af99e4d2 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 e3e3441b3e..8d064c1ef3 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 70526414bb..934aece46e 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 e6c390cd22..a0f33f1841 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 dadfdaa478..6b43f932c4 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 ea5da83872..28995db8ea 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 5ce6d57598..7255d64887 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 8f1782b0a5..8e53a957c4 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 5939bf7ced..18a1275ca6 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 7b8c08f194..4a0141b106 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 ebb693672d..39355b579e 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 b739263e64..f44a76b71c 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 3b11f5130e..fd98d36ae4 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 3ba27a7f9e..22c9714168 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 1af80eafe6..85efaa5ad2 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 96a532ceef..33ed245739 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 e4a51cb833..e50dc20676 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 ef5e1af787..479adf4c43 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 3328313d9a..8ed6c637fc 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 ae4bf10d5a..e90116c57a 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 1df3f180e5..c05c6f6cef 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 380cfb5b59..1f715dad2c 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 b8a77540cb..9a5592bf40 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 62e820b703..26551ff830 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 8fd995882c..aeecba0db7 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 f0e0e3dfe7..046727de88 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 ba99a36130..382e4208ad 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 10d6c09922..7dc7803af0 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 88d04af91f..615c9b827a 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 1507e8ce58..a95cf246c1 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 c16822e0a4..92cc4343df 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 09e33f5c9b..8f9f5616f3 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 a2c28b3153..7f82682dc5 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 191bad9794..f4ad8abbb8 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 c80bcbce99..d9724fc64d 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/1789] 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 51874b7a23..22812240f4 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 d47d7f39eb..5ce81dbe84 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/1789] 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 0000000000..c094aadda6 --- /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 63e9a82ace..bfb9493112 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/1789] 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 c094aadda6..3479534345 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/1789] 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 a440588e85..7c117d13f5 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/1789] 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 3479534345..5918003a71 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/1789] 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 61482ac786..b95ba0e92e 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 2f5371bbfb..df10aa7d4b 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 94984336fa..ea4aa61efd 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/1789] 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 d230b21108..24ecfb9565 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 e98e512b31..5df6ca95e2 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/1789] Fix - deduplicate config --- conf/config.neon | 6 ------ 1 file changed, 6 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 8deb4638e1..548aa5974b 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/1789] 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 548aa5974b..b82a722ab9 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 176d083301..7cfb651b7d 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/1789] 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 fca60cc869..c3c3b2ee25 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 e26aa8853a..53ceaa3443 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 e6506a4a21..78b822ca3a 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 3a12dcdefb..0d5cdf4898 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 4c7410e2f0..390092a857 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 7391715313..c4715616e7 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 90ef29825e..55addb6d90 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 0000000000..fbeac4f3a7 --- /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 d99bbf8955..7887f43191 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 63d7eff0d8..b1f2c18656 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 dbb69f8e9f..4073809a23 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 ad10221352..ae1e86fe8c 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 600405d363..294cc94b62 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 3e111949cb..8e8447ecac 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 38b4bae4e9..151945e921 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 6582c358b7..4b843829ad 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 e81c0b22dd..0db311786e 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 ac2f0b9bcd..51e6ccaf46 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 f23f2f1234..91964e8eda 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 f3f2d38965..b28625e3c3 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 867f3c1dda..441d4a36c3 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 0000000000..9b941088bc --- /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 c34a3c7cfb..e7bb397ace 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 9a78dc5cc7..7670f1962f 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 57577364cd..a05182fa66 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 e7132ecfb3..ca96ebae94 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 3ac0a41c24..ec682c3e03 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/1789] 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 7887f43191..b6ba30dbec 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/1789] 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 9f2230dd6f..11dd4fa773 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/1789] 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 6c6db632ac..6baa6e0c6b 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 c3c3b2ee25..f10fc22508 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 18db804030..a52fde51f6 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 79445fdbe1..0486d11048 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 8ec65fd9d2..984d3615b3 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 8b8aa99567..9e6644c0f1 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 62371a0e85..259ede81fa 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 992d53d13a..aef7210140 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 351d90b467..69d7a7a1a6 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 4c98f8d596..7f81cd1363 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 d26ea0c4be..79e85fda5b 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 0ff0f8f2de..14f7061aa4 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 91afcbaadb..bdd5ed8d63 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 67684abdab..4333954ff3 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 09e9968a1a..5aa65587de 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 58b63fe1c5..eaf63ef8a1 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 870f0b7c66..2dd98f2951 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 7c60edd6cc..425f75edd6 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 5cbdbd2907..4d14c56ced 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 7e2b402bf1..0f47e2c746 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 b66c18b805..ec9f1b3b9d 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 b6ba30dbec..550be0da4f 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 ed04458668..5303d38b0f 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 ce6306bada..5a75f85a8a 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 8da0dcb5c6..0745deee78 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 c84670be53..ab121486a1 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 0f4cd81a03..69863e8666 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 4b980e21d7..32a484c3c9 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 51a89fc329..b6e638c979 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 8698e6196c..c447875580 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 2982f71a62..3b2598c368 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 2a953cf36b..78f31cdce6 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 cd549f67e3..e04381033e 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 e90aee6b80..8994a4754b 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 1925232132..4f0d2ae447 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 c21790b738..d8fb352e52 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 24dc76e186..d1ca216791 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 415e04b748..c4030961cf 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 67eee30157..4f45dbf9fd 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 1706bdb76e..33612ff02c 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 80ef4bccdc..0f15683606 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 e51cf45631..ce30d8983c 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 93b9989102..6d987f6342 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 45ff96e2c9..d392b20500 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 90859848d0..e1c0c8db09 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 9eed739399..d10eee8e48 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 0000000000..d385b739c9 --- /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 0000000000..843530132e --- /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 8877e53e31..255d9f9c02 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 0000000000..e28e91d1d8 --- /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/1789] 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 550be0da4f..9b9a9132e1 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 0745deee78..2aa2aca526 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/1789] 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 84fd7a8252..fb6a5ad968 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 cb79c9cca5..8f3ecc86bc 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 517728b3cd..d347559d7f 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 7cfb651b7d..66fc2ae427 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 c48b2e439b..f1c6b0725f 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 302dab88f6..7afc4bc874 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 a344e4349f..55ef907799 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 0000000000..a88d384a73 --- /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 0000000000..68a513f295 --- /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 0000000000..f512d22fc7 --- /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 0000000000..b304e89340 --- /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/1789] 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 68a513f295..8d77cb01b4 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/1789] 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 fb6a5ad968..00e7b08f18 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 d347559d7f..3842191a09 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 66fc2ae427..3518d5ba4a 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 f1c6b0725f..d81f6151c2 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 3b4330a297..b507b05ae6 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 c9c32db584..4194d4f4d3 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 b149c39ab0..f820c64aff 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 1c6c9536c4..26d74b1c73 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 0000000000..e37d6be94a --- /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 57faddd9bb..f39b3e4fac 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/1789] 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 3a179206ba..4181f6fa86 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/1789] 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 00e7b08f18..a9f20432b2 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 bb384e9511..4097e0d85d 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 3518d5ba4a..2f3ef3b668 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 fbedaffbd7..50a0c99e85 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 2b2cec639b..2417944d1a 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 439f5bdd71..86f8725573 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 227044f2d7..5cbe1c95eb 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 0000000000..8bb5c9da96 --- /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 0000000000..8141c6b1f8 --- /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/1789] 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 84015d215a..85b8974449 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/1789] 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 50a0c99e85..c49d6ce4ea 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 5cbe1c95eb..6dc1acf00c 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 0000000000..d1652c132b --- /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 0000000000..f36ffbfb1f --- /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/1789] 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 6dc1acf00c..78415d4616 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/1789] 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 6d52d72f95..58a63f6e85 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 131ee1ec91..acdbeef79f 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 223fd616b0..ace29c0b95 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 0000000000..46ad5bde9c --- /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/1789] 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 907c83e394..347bdb2629 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 91ff9d9360..8489946fa7 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 0000000000..fa6f36463c --- /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 0000000000..d19443e644 --- /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 0000000000..79cd19f116 --- /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/1789] 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 8489946fa7..b8396455d8 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/1789] 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 c49d6ce4ea..a26ea1cb84 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 0d13ec6d87..f33e974e08 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 0000000000..887ee2fbb0 --- /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/1789] 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 a26ea1cb84..19a76e042c 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 702fc0f299..fcc4671708 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 f33e974e08..b86302f453 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/1789] 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 a085f8f07b..185c2c5530 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 46164fff02..b3092b7a4a 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 d4de8dbf84..fa623d2d5a 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 96ea886257..dae30a1039 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 a0c4e977a0..018b6b1c98 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/1789] 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 b8396455d8..190d3e296c 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 b3092b7a4a..e81665cf8b 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 dae30a1039..efb2481bb6 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 018b6b1c98..6cf1e7057a 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/1789] 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 190d3e296c..fb5a46f209 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 e81665cf8b..ca8bceb0fb 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 efb2481bb6..2ab4973cef 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 6cf1e7057a..c60ff3ce6c 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/1789] 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 545fac6ad2..1029bcdba0 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 fb5a46f209..8d61831c38 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 0000000000..4b602b5fa1 --- /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 0000000000..373e46494a --- /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 0000000000..a0c83d7e3f --- /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/1789] 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 4097e0d85d..081dc1a4a6 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 a8d1b1e476..74c7f5507b 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 e55f429317..b3b8689b84 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 49d73e9c5c..ba18681762 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 a70cac8f8e..9dde0cdd29 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 d8c16b3e0e..6aaa554d52 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/1789] 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 081dc1a4a6..688c2a4f5c 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 74c7f5507b..a5606a5b20 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 7fb999403c..cfb270cadc 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 241cef7c6c..58d72696ad 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 b3b8689b84..7cdffa7704 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 ba18681762..7106d65997 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 9dde0cdd29..ba4c47f576 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/1789] 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 a5606a5b20..f71222e443 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/1789] 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 f71222e443..cd6362f8dd 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 7cdffa7704..089014a7ae 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 7106d65997..1f53ff745c 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 ba4c47f576..5aa52e9082 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/1789] 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 cd6362f8dd..71e807ecd7 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 089014a7ae..e8c07ca171 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 1f53ff745c..1501b40f79 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 5aa52e9082..152e77d8d7 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/1789] 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 347bdb2629..72ff318df0 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 07e39383c7..56f19a7203 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 a396245146..1d93a8a299 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 d5a3fafd67..b6ab1b092b 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/1789] 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 72ff318df0..0d9fd56ff5 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 688c2a4f5c..6ece4804c1 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 390092a857..68752bfab1 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 0000000000..abbc274698 --- /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 0000000000..c1f002c3b3 --- /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 0000000000..cd3a54c9fd --- /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 0000000000..b5718fb844 --- /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 0000000000..c6e140604c --- /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 0000000000..c5a50f30dd --- /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/1789] 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 8d61831c38..0310ae11f8 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/1789] 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 ca8bceb0fb..f7907395ad 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/1789] 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 6ece4804c1..732370c728 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 9c1efe6c4a..a0a07a2c20 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 2cb7788d59..71daff135b 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 fd60ba01a4..f5140a487c 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 c26dd6f290..c270de3626 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 72914da2e7..625c3509f7 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 eb34897e10..5126d4bf15 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 a7851f6c52..017065a4a8 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 c57774efed..3a9d430ec0 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 2101cedb5c..196e663c7b 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 0000000000..04a04e2862 --- /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/1789] 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 625c3509f7..ef698a4061 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/1789] 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 f5140a487c..ef9ce469b5 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 5126d4bf15..05963f5576 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 bb942838ed..e66591168e 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/1789] 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 8bafa45fd1..5cb51461c6 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/1789] 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 f0fc56a9b1..c559efc208 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/1789] 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 9e3e86f187..257883af2c 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 58a63f6e85..402f8c7dd1 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 a52fde51f6..c0fa6c5585 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/1789] 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 0d9fd56ff5..72d297bfb3 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 732370c728..38a1518a30 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 0310ae11f8..a1409ca917 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 68752bfab1..cc687fdfcc 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 0000000000..1c61442c9d --- /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 0000000000..cdfc6759e7 --- /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 0000000000..57f84a3941 --- /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 0000000000..9ff4c1337a --- /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 0000000000..543e0d9d70 --- /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 0000000000..149d43a854 --- /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 253ce69174..f6f76e5610 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/1789] 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 58a4c837ce..c05c3fd4ce 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 cbed841d93..06578e01d3 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c05c3fd4ce..a252b22197 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 06578e01d3..33d5165355 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/1789] 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 2f3ef3b668..84a4006566 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 ef1e244ac4..d926420e77 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 f84fe900ce..d8f30898fc 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/1789] 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 b0393de109..03420615cd 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 3c13f9205b..6d16f21aaa 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 d38dd2726e..450717dbff 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 e86c7738fb..d1c6348bc2 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/1789] Update react/socket --- composer.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.lock b/composer.lock index 33d5165355..db6184e878 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/1789] 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 14f81570ca..e08bad1432 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 0f8e5ba463..b25b7b1e0f 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 0000000000..e343d801b1 --- /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/1789] 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 a252b22197..2ec74216d0 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 db6184e878..3de95f5a0f 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/1789] 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 450717dbff..b19b6c10f7 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 d1c6348bc2..59d834e9c7 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/1789] 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 2ec74216d0..fcf036b458 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 3de95f5a0f..350eeb5e8c 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 0000000000..ff7332693f --- /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/1789] 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 fcf036b458..fdfd4e753d 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 350eeb5e8c..6af4110a97 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 0000000000..9ff7a2b3de --- /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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index fdfd4e753d..4f382b6452 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 6af4110a97..a6da61b2a5 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/1789] 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 de637bfc79..723a3ece2e 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 4f382b6452..ed74282aee 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 a6da61b2a5..988ff4494d 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/1789] 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 ed74282aee..d31bac1381 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 988ff4494d..31c6ca2800 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/1789] 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 0000000000..a756e468d5 --- /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 a227b7cf3c..45b3efe97b 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/1789] 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 d31bac1381..e5f77ae753 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 31c6ca2800..b50891bf2c 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 0000000000..0732eb0e55 --- /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 0000000000..6f6f0dbe29 --- /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 0000000000..a3e9e10965 --- /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 0000000000..ad9b64c445 --- /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 daf6990e1b..8dbb2e108e 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 0000000000..39e7917def --- /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/1789] 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 38a1518a30..4e3d04027e 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 e08bad1432..ff3f1966e2 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 0000000000..12236c20cf --- /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 2cb6f29448..e3a0b751ee 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 0000000000..17bf3d44c1 --- /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 6aec5018a0..a8c0fc19ae 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/1789] 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 2b702bd9a2..ab3bce12d2 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/1789] 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 ff3f1966e2..817316d16f 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 b25b7b1e0f..6ae28ec9c6 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/1789] 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 e5f77ae753..d033daaf21 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 b50891bf2c..46635833ba 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/1789] 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 0000000000..0b48d36ebc --- /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/1789] 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 d033daaf21..cb9caba6f1 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 46635833ba..fc9f3d1ef0 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/1789] 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 d6f5a8251c..c44d17a250 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 0000000000..7a97062078 --- /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/1789] 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 2b94fda6e2..0334aead77 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 6938ff8000..8f282899ed 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 40e6d99d9f..957b3557de 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 3a01594ded..0d2eaec66d 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 001ec26d21..655c9d7d10 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/1789] 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 cb9caba6f1..860315948b 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 fc9f3d1ef0..8fe97b97d3 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/1789] 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 a9f20432b2..8fb6e4da6a 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 8052e5f5de..d23705fa92 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 4e3d04027e..32b0127eb1 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 84a4006566..05d6d79f0c 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 0000000000..f2a9af9989 --- /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 0000000000..287df68634 --- /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 0000000000..e69de29bb2 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 0000000000..2d18f0066e --- /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/1789] 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 da1a00a16a..dd4af579ac 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 8e98af9d78..16566ee530 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 460137066c..dacccd3e31 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 292d00f7a0..9d37625612 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 deacff9371..ffc6aa26d5 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 bf704ac725..86420630c2 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 0000000000..43480baf5f --- /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/1789] 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 5601c024af..e5bd2bbd57 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 58dadbdd4a..09114d90f8 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 540b932531..7a4e217287 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/1789] 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 931f3bfe02..e5dd75c186 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 2bec6dfa21..f47edea747 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 0000000000..f242b77eeb --- /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 0000000000..f242b77eeb --- /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 0000000000..f242b77eeb --- /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 0000000000..f242b77eeb --- /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 0000000000..f242b77eeb --- /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/1789] 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 860315948b..9c2e3570b9 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 8fe97b97d3..cbfc81e3da 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/1789] 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 8f282899ed..0cfbb26e2e 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 655c9d7d10..f4ed3b7ffe 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/1789] 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 d5678343b5..fa22bc9c06 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 975ea62613..322c33547f 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/1789] 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 9c2e3570b9..3a722a9282 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 cbfc81e3da..7039f2bc07 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 00a2352c99..c50d21d878 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 ed78c46f17..c8ead1d217 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 8f1828cbe4..550ea9e019 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 03cbdaa49d..953ca5f982 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/1789] 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 e5dd75c186..55926064a2 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 0000000000..f242b77eeb --- /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/1789] 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 e5bd2bbd57..b279185906 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 4d0ca33581..1b67568efa 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 0000000000..07fe77ade0 --- /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 0000000000..e9ad4e2464 --- /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 0000000000..580ad7f33f --- /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 0000000000..582062deb7 --- /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 45ddca1767..f16d288869 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 0000000000..85c59c4fae --- /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/1789] 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 b279185906..66a60954b6 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 582062deb7..ea9486a77d 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/1789] 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 f2a1652e42..af4a00fca8 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/1789] 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 c8125243ef..d91ab1ff1f 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/1789] 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 525b65caed..a306833e8d 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 0000000000..911490fac7 --- /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 0b8bff2efb..2d1aaa237c 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/1789] 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 3a722a9282..11803e18a3 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 7039f2bc07..71beaf624c 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/1789] 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 66a60954b6..69b72a1772 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 ea9486a77d..0d883cd6ba 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/1789] 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 b297f3dc72..18a66da75c 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 1b67568efa..1d17f9ee48 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 0000000000..3acb253f49 --- /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/1789] 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 69b72a1772..9bf24f318d 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 0d883cd6ba..afd5224ae0 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/1789] 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 5303d38b0f..a52f3829f8 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 49b56548cc..d49987e3c5 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 0000000000..05e4d85fff --- /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/1789] 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 9bf24f318d..356d0b30be 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/1789] 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 32b0127eb1..dc3e3b85f4 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 0000000000..ce6d31b581 --- /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 f1d3d5902d..a75873ffd5 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 0000000000..d37f48cf07 --- /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/1789] 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 d23705fa92..1382d99ee1 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 72d297bfb3..c60247afb1 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 7c2118d058..a885d648b0 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 39b0dc509b..2a3a810fb5 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 a1409ca917..1949fce656 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 cc687fdfcc..be4ef76e9e 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 71e807ecd7..5347681a90 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 58d72696ad..1f7fe5021d 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 0000000000..2523e3a9b4 --- /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 1c61442c9d..e8c44d416e 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 cdfc6759e7..ddb3cf254d 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 57f84a3941..157c46f7f4 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 0000000000..1f6d6f1f7c --- /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 abbc274698..788c252d47 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 cd3a54c9fd..bd5de407ba 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 0000000000..f381cd0dfd --- /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 6a8c6b517d..a5aac09b6e 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 1501b40f79..fb443854df 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 0000000000..58ddda39a4 --- /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 543e0d9d70..a2c07386e2 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 0000000000..4e775a4857 --- /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 c6e140604c..887cebd583 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 0000000000..c19a36419a --- /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 0000000000..7ef678f8fb --- /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 0000000000..c9bf36e246 --- /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 0000000000..e41413653a --- /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 6aaa554d52..6628e0db7c 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 0000000000..94e019280c --- /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 149d43a854..504033696a 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 c5a50f30dd..9bee89824e 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/1789] 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 dc3e3b85f4..704b061171 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 0000000000..b0b6441c92 --- /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 eafb0e946d..b2f3e2a08c 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 e754794566..1db002fd94 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/1789] 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 1949fce656..8fee0f5f9b 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/1789] 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 c60247afb1..e43d074210 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 8fee0f5f9b..fc6a44ee28 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 0000000000..d0235e975c --- /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 0000000000..773f6c30c3 --- /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 0000000000..57a93beb5a --- /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/1789] 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 eccbd3eb1a..eaa39f1d5e 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/1789] 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 be4ef76e9e..9527e526b7 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 4e775a4857..59c1cb6aab 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 0000000000..9855c17844 --- /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/1789] 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 76fac3fa37..c234e8e2d1 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 0d5cdf4898..d6d69179d5 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/1789] 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 e43d074210..efbc7b7653 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 704b061171..9f62638b65 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 fc6a44ee28..e639533f64 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 0000000000..e732f6d9e6 --- /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 56f19a7203..8fb28e0888 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 1d93a8a299..acaf1974b0 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/1789] 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 e732f6d9e6..ab6f5ccb00 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/1789] 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 efbc7b7653..1399394032 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 e639533f64..43ecfdd0a5 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 0000000000..5cb7c1ecd9 --- /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 0000000000..33a3e80780 --- /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 a5aac09b6e..f3e8929e16 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 0000000000..f23e120458 --- /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 0000000000..dbc7906da5 --- /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 0000000000..0b67bfb4fb --- /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 0000000000..83c0f0b488 --- /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/1789] 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 68ccf90ed9..58ae58ca2f 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 8e6d32054f..4b21f92451 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 f3e8929e16..ec6abc47e7 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 255d9f9c02..3f29976a12 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 0000000000..1aa5c3b428 --- /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 7697f826e3..a07611980b 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 0000000000..621a02e3f9 --- /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/1789] 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 0cfbb26e2e..03bbcb6cce 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 f4ed3b7ffe..694a7cc886 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/1789] 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 e9aebc1c26..2d193095d4 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 04ca707933..82c92a4ecd 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 c1e85d214f..af02a79635 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 0000000000..b94251a495 --- /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 581d512890..7ec5a8d7f4 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/1789] 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 11803e18a3..f6cb9c01db 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 9f62638b65..255be78dc2 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 f10fc22508..cb4dd6b858 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 0000000000..7f6a43930a --- /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 2df15b78f0..3ecbecaa7f 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 0000000000..6f331c97ba --- /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 0000000000..0e7b8663aa --- /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/1789] Fix build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6670f1cea2..3282ee2c4e 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/1789] 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 a885d648b0..b22d651f99 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 0000000000..ebb4220372 --- /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/1789] 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 ae47818512..4d89a240be 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 0000000000..b8ae395d17 --- /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/1789] 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 0233e1e422..d7651c9202 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 278470b466..0e541ca5b1 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 21971571f3..bda67d4725 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 47256373d0..994f11ba06 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 53fded3322..239a5e3781 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 03420615cd..105fff7588 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 c6c4934b44..723e8a93be 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 85b8974449..f522ea446e 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 6d16f21aaa..d996728a38 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 d91ab1ff1f..4304d27005 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 b19b6c10f7..fb498cd9bc 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 59d834e9c7..0080b3d697 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/1789] 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 105fff7588..bdc15d968a 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 d996728a38..2bf67cb14f 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 fb498cd9bc..24760de756 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 0080b3d697..75f5de1627 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/1789] 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 f6cb9c01db..7e859b19e4 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 71beaf624c..aba98b1475 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/1789] 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 255be78dc2..dac1e96e5f 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 5fa801ddec..e02bc5ed2c 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 14c462c39f..d4e00547a0 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 b5863a4b80..e3f2a5e310 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/1789] 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 f7f2b6cdf6..7bef8b99db 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/1789] 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 cb4dd6b858..3a251908c9 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 b22d651f99..e7b3a0abab 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/1789] 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 77ff81fd1a..cdbd956fa2 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 57a80a5a2e..2c7d3f581a 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 7afc4bc874..0707a5bc6d 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 d8aaea6218..f0ad86ff8c 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 55ef907799..7fb27f8351 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 ea9be27be6..1ba0c04ef5 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 ce21f571fd..d4c4b53ac3 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 dae4e11140..2f1aaa96a0 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 ab83f1d019..d8281b1d88 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 4be69c0ac0..f1573bec6d 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 0bfcebf956..dfe2a48d4b 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 5b5f3545a9..fb4ab23162 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 5cf96d8ad2..a2c04dc054 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 1494208b5b..c019d69240 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 b8ff7713bf..a4424b69ac 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 7b24c91c52..a5ae2ac291 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 9e4fc3ebf1..b7021dfe92 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 b85150e267..4fbe76d20d 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 edd8d3eef6..db5d8983f5 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 472683ffb1..a571338e18 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/1789] 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 51ba629e16..df14e33493 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/1789] 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 3a251908c9..36eedbe816 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 7506682426..2edde64fbc 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/1789] 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 90df53887b..34dfd1efe1 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 3a6d194395..6eb1f57604 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 d234baa6b1..3fb3cbf239 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/1789] 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 723e8a93be..f2f75906b3 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/1789] 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 8ef6277fe1..9ece8025a0 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/1789] 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 5540500714..f205db23b6 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 03a80e73bf..0000000000 --- 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 020f713708..0000000000 --- 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 f819307398..0000000000 --- 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 5582923fa2..0000000000 --- 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/1789] 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 dac1e96e5f..4558955396 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 0000000000..e108246128 --- /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 5d3c7c2576..9fcc9c9e88 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/1789] 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 36eedbe816..c3f58e1c92 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 e7b3a0abab..e11eeff70a 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 c9a0c52154..36a752735d 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 2f1aaa96a0..6013737759 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 381a2e1de6..b93db1ea45 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 4a227ffd07..81b4296100 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 b7021dfe92..5ff12a4206 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 db5d8983f5..a0af548147 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/1789] 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 4558955396..d1b5182fe0 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 0000000000..dee78b35d2 --- /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/1789] 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 8f9f5616f3..dbfb14973a 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/1789] 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 520b426424..9865d3d1ce 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/1789] 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 d4c4b53ac3..d20a8f4b90 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 f4a6e17499..74fa2eb0ae 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 5ff12a4206..87784c71d6 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/1789] 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 e11eeff70a..a15060536b 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 bb9ae78f2e..985777317e 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 48722eeca5..b57afc5fba 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 6013737759..174adae5c1 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 d8281b1d88..1d9537d274 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 2d193095d4..51c07fdfd8 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 3777b5d6e5..4b43745923 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 68ac27fbf2..505c57d7c6 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 d8e8b00fcb..75657f232f 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 b0b364030a..66aaa94026 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 0070554896..67bcd8f88a 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 b60730e828..5185fbccc1 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/1789] 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 d1b5182fe0..4d897a4587 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 e3f2a5e310..ed0f1840a0 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 f820c64aff..d59549b9da 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/1789] 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 dbfb14973a..5538370ccb 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/1789] 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 1645698a92..ae22e5ccdc 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/1789] 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 cdbd956fa2..fbfeafa608 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 a15060536b..345a323d03 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 d4e00547a0..7481574450 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 c365e0581f..e6ac71ded5 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 df1bf3499a..1c4436ca46 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/1789] 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 c3f58e1c92..b6a38997a8 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/1789] 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 2edde64fbc..7506682426 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/1789] 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 345a323d03..0b7133e631 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/1789] 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 0b7133e631..f32e3345b7 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/1789] 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 71678d99ff..0000000000 --- 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 7995c696f4..e3e82da0ff 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 b0bb271349..0000000000 --- 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 c726559432..a82880302d 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 9b0aaf913d..0000000000 --- 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/1789] 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 8fb6e4da6a..12694539ae 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 1399394032..d7a7cc943b 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 4d897a4587..ae411edcca 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 05d6d79f0c..8b986b0442 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 43ecfdd0a5..7260542411 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 923d65e143..51b22dd564 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 fe40a1bd61..569c776df3 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 e3e82da0ff..c664e1658a 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 a82880302d..542b38f13f 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/1789] 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 103eb129b4..69fc99cbab 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/1789] Fix baseline --- phpstan-baseline.neon | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fbfeafa608..7bbba41b08 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/1789] 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 7bef8b99db..6d23dde7a6 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/1789] 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 78415d4616..056740ed99 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 b86302f453..df40cbac04 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/1789] 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 24760de756..a0c08f6171 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 75f5de1627..5533bd7e6a 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 7e859b19e4..64c143fd28 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 aba98b1475..6ef0c6b966 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/1789] 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 6ae28ec9c6..54b26ec9f8 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/1789] 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 ed0f1840a0..14ade510c3 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/1789] 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 542b38f13f..405e3668a8 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/1789] 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 f32e3345b7..8a992f5c13 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 dee78b35d2..3a1f2cb4ea 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 e108246128..772a3a1c43 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 c7658440ab..f1f9f032a3 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 19a76e042c..a9ea3ff521 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 a1afbb742b..5462137e5d 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 b008e63470..a7894f762f 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 177079ac6c..9b42e1909e 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 4194d4f4d3..874b601156 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 a0af548147..9a14e3aec4 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/1789] 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 8f3ecc86bc..421cafcd9e 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 ff0d6eb8e7..0000000000 --- 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 edffaa5b9a..0000000000 --- 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 64c143fd28..e4e61cd532 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 6ef0c6b966..0aba247c22 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/1789] 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 772a3a1c43..386c903281 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/1789] 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 184cee83b8..470689b7c2 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 93ade0dafc..0000000000 --- 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 44755df63d..0000000000 --- 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/1789] 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 bf46cd56a6..8e530c62aa 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 2417944d1a..e670804dcd 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 0000000000..5a9641fb06 --- /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 552bf901c6..4a18708fba 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 a75b82f714..2f580113f5 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 583c6a5a5f..83e315479d 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/1789] 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 59ebef356e..c76ca0ebca 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 1d17f9ee48..f75730744d 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 eb644eb4bd..506f436c02 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 ed6f552d15..17e49019c7 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 4e52490066..4a8567a2db 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 1b094a1cd7..b6391d651a 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 12176791a3..73f15aade7 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 b9b362172e..ffc9552321 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 a39ffe1436..17aa63957a 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 b7368f06e9..4efe160b18 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 58adb961c1..56c49c331b 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 3299015ca0..a16ed81f04 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 d07a7f6804..bad001bea0 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 0a5c8548be..993f7b470e 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 277b00d9e6..2cf34a5733 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 944d10e7e4..fa5c6a73af 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 737ab3dcb8..0d219b40b3 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 960126723d..46964dc0ad 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 a1d9ad568b..88af4df14d 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 a4c8cc6251..7280c8634c 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 db0110f2f4..e099f4eec1 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 2c1a90f5bd..3265c4a7b0 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 923c0534e6..e81f67b7ea 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 15807d0b1e..f24b810fe8 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 7c677aa8d6..6ab9515b87 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 acd6a616ae..316c8e8ed4 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 55335c6e2e..89bfa50a22 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 835ba4fdcc..00a803d52d 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 33f1200924..37809016ad 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 bce229826a..eb11c5254d 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 706a300680..d773c237f8 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 52261b0e8a..38db914722 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 94bb3238a2..9fde822894 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 92fdfaef5c..0aab6ea591 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 11c2ed6a2a..0c98f24b2b 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 8d7ce4ad16..5b3c17cbb1 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 505557b452..593dd799ab 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 f6c511df33..869f3c2bf8 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 dbb64b29ca..31407e99be 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 ffc6aa26d5..bde2d1100e 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 32d4900686..c7de4bc7bf 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 5734b47928..bc7cd35acb 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 4018d2ca62..0e6d0b066f 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 88d9315d84..38d61a57dd 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 7a697e6ffa..6504b21a6a 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 d49987e3c5..174f8ece4a 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 e670804dcd..a409df4391 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 86f8725573..9b1c1c329d 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 f16d288869..53476ec81b 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 3f29976a12..b5565f4d40 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 27718dc9c7..d46969bdf3 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 df40cbac04..07ea2260ee 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 e55eb6eede..0f6d5b81f1 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 826586689a..75f9465e74 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 ad9fc94be5..533ad774eb 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 41c947e937..190b83798b 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 5042bb336c..a70d03a6e7 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 2475fa3a80..ddc41ed337 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/1789] 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 723a3ece2e..7502680e13 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/1789] 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 b84ff5feb0..e3a3997bf3 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 ec6abc47e7..56017066d1 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 d78066e232..11088d2e44 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 b88d6e5c02..e8d6aa5911 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 580ad7f33f..e5674250b4 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 956d53f055..296baba23d 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 1fbb9547ac..484d0363e3 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 01045e25f9..cc831b87a4 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 4600ae0a13..78d2899b8c 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 6504b21a6a..29f63a6bf2 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 a6c4d33025..e3ee4aa5a5 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 056740ed99..06957666f5 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 07ea2260ee..8c9fc563e3 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 6303bdcdc4..f080bd4be8 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 0000000000..e7c1b2141a --- /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/1789] 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 994f11ba06..b5dc04f6dc 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 f2f75906b3..c6534ecd7b 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/1789] 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 36b9730690..9375c12fc2 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 df03ddeb72..5ef4bd6275 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 4b609e26e2..f00b6ac922 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/1789] 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 9375c12fc2..32c7417841 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/1789] 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 0ea6df31ec..5b4a21c5b2 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/1789] 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 239a5e3781..dd8a44e406 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 1117af27b0..0000000000 --- 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 aad958b3b5..0000000000 --- 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 b9fe1cd9c1..0000000000 --- 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 3ad1d1a3b1..0000000000 --- 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 fd17dda8f2..0000000000 --- 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 fb4e9bb6d8..0000000000 --- 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 993f2b65ee..0000000000 --- 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/1789] 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 0f8d05a8d8..f18941039b 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/1789] 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 f18941039b..0f8d05a8d8 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/1789] 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 6236c3c63f..e3dc297d4c 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/1789] 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 e3a3997bf3..70781bf86c 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 f75730744d..98080c2b67 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 56017066d1..dce99191c0 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 025513c4bf..c3476c32fa 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 5bd12fdc38..f752a03270 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 2892179613..87d990b96f 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 0d2eaec66d..a30f261fae 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 8cfe1f92db..555c1a50ae 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 bde2d1100e..a31fe488e2 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 d7d1e64de5..ff2f652cac 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 d4f646deb5..bb28ed1eb1 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 e1c99901de..ff6e8c9aca 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 87693235ad..b01b9b2412 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 e17c06a69d..3f60827491 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 5351fc8302..4b39b689c1 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 98c3737887..5a32351986 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 c7cb4766ad..ed14c55755 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 174f8ece4a..7dc89a5f58 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 020000b38a..2a7a309e1a 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 9ec40a74a9..b6a79b6817 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 8d0506f9fc..88ce97861e 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 0606d7a275..d93d7b6ffc 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 fa1ec0f17a..30f1a93884 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 93c6a67b9d..cbdac548bc 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 75f9465e74..916532a7b9 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 f080bd4be8..d3b5a5a237 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 449d409b2c..a71fa7d809 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 a07611980b..2809d62a9a 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 e8cd8306de..593aa13f97 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 14e4779f22..0ae3edbc3c 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 5c92162410..7f99a5ae72 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 c7a90b03ab..a72e7d23f2 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 09f34d1d40..fea9ffb9e1 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 7af71ae1b8..ddac393d08 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 1949d4d8b6..4335dabd72 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 f93e860eb8..78d5c4219d 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/1789] 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 e4e61cd532..84ed1845a0 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 0aba247c22..33f2c2e5d0 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/1789] 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 32c7417841..7700ceb911 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 d7651c9202..9078243232 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 0e541ca5b1..882a0eb6ae 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 bda67d4725..5989cfb021 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 b5dc04f6dc..3f2e1b80f9 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 dd8a44e406..1b32f42e3d 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 5ef4bd6275..fa9cc1f845 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 bdc15d968a..a262c8e2a2 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 c6534ecd7b..03926a683d 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 2bf67cb14f..39fc0583d5 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 a0c08f6171..6dc9e9e0da 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 5533bd7e6a..25a7bebfd2 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/1789] 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 bcec59c628..28b0091af9 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/1789] 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 f6cb9c01db..feb31fdf19 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 71beaf624c..51285a4922 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/1789] 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 bb97758ff6..d4c45e5934 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 5b4a21c5b2..fd867cbb48 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 b2feaaa21e..52c510ff2d 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 eae3007e67..9318843ada 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 e86af17617..3d735839e3 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/1789] 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 6d23dde7a6..9dbc3e3192 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/1789] 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 7502680e13..79dc694253 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/1789] 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 c9a0c52154..8e6eb17f61 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 0000000000..f2a110f048 --- /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 817316d16f..4eae1e59f9 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 19a76e042c..e50021d7ed 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 86f8725573..aa026a0f07 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 0000000000..f513b60f61 --- /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 b86302f453..f95708eda3 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 0000000000..f5690b2c72 --- /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/1789] 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 c8e839e478..3942cc1b40 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/1789] 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 2b81792b99..2f9b881057 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/1789] 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 e3a0b751ee..2dc82fe7aa 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 05fdf38f0a..ba72a0d584 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 1b0e578bfc..544473f615 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/1789] 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 255be78dc2..236aed720d 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 34b91d99a5..cb8c2d6543 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/1789] 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 3f29976a12..8bb53330d4 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 0000000000..6d1c7fb276 --- /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/1789] 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 c6c4934b44..47b7b6e300 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/1789] 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 131592080a..03926a683d 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index feb31fdf19..670a2ffb39 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 51285a4922..dcea32fd8e 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/1789] 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 dae4e11140..1eec9736d1 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 10da4dd484..fd65dc9e51 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/1789] 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 628bcb0d3c..fb94b88ab0 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 62708a2de3..51a1fc9d85 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 694a7cc886..4b17f15ed4 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/1789] 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 356d0b30be..fa2a2f2ef9 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 e472a0d68c..38b1884bf5 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 09114d90f8..54fb89c2c7 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 7a4e217287..f66d50c140 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/1789] 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 fa2a2f2ef9..c7413dec9b 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 6307af45e9..8ad670a7f8 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/1789] 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 874b177716..f80ada4ad9 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 cdb258d613..ac4b6e402b 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 a87f7879ab..59a2a26b21 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 8ef7ecbb88..8f2ccac689 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 c6c9c5b6d7..ad8a1c2a13 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 6040f0ee06..061e002a46 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 3853794348..5ab51870a8 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 9b4fbf22ba..08ff407033 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 402d192a7d..c45642f0d2 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 b619768b6d..a21dcb7d23 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 1a693eff8d..1696d39d48 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 766cede665..9db6c41cd0 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 a749a6fae0..bea1d953a0 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 da064c82b7..7d25897553 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 def99b0e94..8deb186895 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 ec682c3e03..60e1046d1b 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 1a8ac35969..f79dbe88a4 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 0000000000..4c9d1ab563 --- /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 e5a205ac0a..413e1d5f2a 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/1789] 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 558096b86b..375c3d38d3 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 6ec7d665f7..529d433dc1 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/1789] 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 47b7b6e300..c6c4934b44 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/1789] 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 670a2ffb39..02ef955af2 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 dcea32fd8e..a72a0e69ef 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 17aff2c148..607a23fda2 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 05963f5576..f8d27e2612 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/1789] 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 236aed720d..e946b92542 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 77ff81fd1a..d06f9c4923 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 c44d17a250..4672fd1a96 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 ef684865fd..f6e0b6c65e 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 6303bdcdc4..2af1147c65 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 0000000000..49dce6d7a8 --- /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/1789] 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 92cc4343df..8ae601b832 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 0000000000..a6ee71f048 --- /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/1789] 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 0000000000..4c96faacb6 --- /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/1789] 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 0000000000..4002e1ce16 --- /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/1789] 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 4efef75454..02d1fd848e 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 0000000000..8c2f4ca526 --- /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 0000000000..40c8f05b92 --- /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/1789] 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 8c2f4ca526..5ab5e725b9 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 40c8f05b92..f0e25e9296 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/1789] 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 413e1d5f2a..f416539531 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 4c96faacb6..1280620263 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/1789] 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 0233e1e422..51e52a365e 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 278470b466..06213ad052 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 21971571f3..8084523280 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 47256373d0..fd975528a3 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 53fded3322..c9357fa047 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 5ef4bd6275..6d6af362f1 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 03420615cd..afc5c0db7c 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 6d16f21aaa..8b84920a73 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 b19b6c10f7..e8b9e1ec39 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 59d834e9c7..292f8ccf27 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 6d2e16963c..e817604542 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 02ef955af2..eb0fc3b709 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 a72a0e69ef..0b88dc696a 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/1789] 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 0000000000..3c83a57e57 --- /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 0000000000..d603b6d3ef --- /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/1789] 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 0000000000..fc752007d8 --- /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/1789] 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 02d1fd848e..eda169fc20 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 8b986b0442..d26f5d2092 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 8a992f5c13..360f5ef6eb 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 b507b05ae6..6e88b1ab68 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 874b601156..df47758569 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 d59549b9da..a27d75468f 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 0000000000..cd4ceb5d85 --- /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 0000000000..d1569b1d5b --- /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/1789] 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 92ec4ec6e8..1d35a96375 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 af4a00fca8..9653a2cef4 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/1789] Update phpstan-strict-rules --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 4af6cdedaf..383b1bf73d 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/1789] Update phpstan-deprecation-rules --- composer.lock | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 383b1bf73d..87b3132246 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/1789] 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 d49987e3c5..45b461725c 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 0000000000..703fb32b87 --- /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/1789] 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 45b461725c..4beb131a6b 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 0000000000..d7cdb6c163 --- /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/1789] 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 e946b92542..6f18952254 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 375c3d38d3..d7f2c1f8ec 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 529d433dc1..1b234d2280 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 a53cbbc685..0000000000 --- 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 b84ff5feb0..236c066b72 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 0000000000..62b1748d2e --- /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 7749b327f1..0000000000 --- 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 9778b786e0..0000000000 --- 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/1789] 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 c7413dec9b..fd243efb16 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 0000000000..520cf772bf --- /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/1789] 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 eb0fc3b709..d0c4b970db 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 0b88dc696a..7bcc05980e 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/1789] 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 2cd14e4298..46ba2c3cd6 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 87b3132246..eb3567da21 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 12694539ae..b07884cc2a 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 ebb430c312..3c6e1d4072 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 d26f5d2092..528e437a21 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 cbc07aa2be..b487902192 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 71a1e04c07..1973f7e303 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 4af1282a78..0000000000 --- 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 402f8c7dd1..75857e5eed 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 c0fa6c5585..ccfd16f32a 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 fbdbaf2f97..3aa75ee5fb 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 12e03fe1b0..e8af52e322 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/1789] 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 fb94b88ab0..63e44b3ea2 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 236c066b72..c98129fc99 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 0000000000..8f31554f43 --- /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/1789] 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 0000000000..82382370fd --- /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/1789] 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 82382370fd..5525195f4b 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/1789] 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 b07884cc2a..dae0bbb2ab 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 d7a7cc943b..29234ebe71 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 3c6e1d4072..94074a853a 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 528e437a21..0d8cfd70a7 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 14ade510c3..0bf45b07a3 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 7260542411..5bc8934959 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 569c776df3..2caa53394e 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 a27d75468f..39155bdb6f 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 fa8f2a50fd..0000000000 --- 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/1789] 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 6f18952254..05a71dbe97 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 43ecfdd0a5..ee4fcaa2a2 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 e8c44d416e..b37d373772 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 ab6f5ccb00..a17ef3d200 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 788c252d47..e05c4c676b 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 9ff4c1337a..7766e03bd8 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 a2c07386e2..74f70ca72d 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 59c1cb6aab..7839c99123 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 acaf1974b0..b1a1bb39ca 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 f23e120458..a16f6ac23f 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 dbc7906da5..08d3bb1c02 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 b5718fb844..7b36d89e90 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 887cebd583..3e6ff6c953 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 c19a36419a..76b4342abe 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/1789] 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 d91ab1ff1f..e87b7b38c8 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/1789] 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 6236c3c63f..40668741d9 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/1789] 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 a96c45d795..0047c107ed 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 0000000000..025eaefe2e --- /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 0000000000..c2312fc0ed --- /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/1789] 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 ca7c8f9c2c..ff39bfc9d7 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 3bfe998b6e..398e241bd7 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 c2312fc0ed..41bd197ed9 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/1789] 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 adea89e232..04cda78057 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 0000000000..c209e8ffb0 --- /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 5525195f4b..4f9a5843f1 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/1789] 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 24561b5598..e8e9e3a457 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 2af1147c65..6d374a6f1c 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 0000000000..82cd7f66ae --- /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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index d0c4b970db..e121521cce 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 7bcc05980e..9fd242603f 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/1789] 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 10615f2928..82240f467e 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 0000000000..1926a10ac5 --- /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/1789] 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 4beb131a6b..c35966b09f 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 0000000000..6c4b8abea1 --- /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/1789] 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 0000000000..863d17cb85 --- /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/1789] 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 0000000000..a0049b55fc --- /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/1789] 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 0000000000..17f51f2b26 --- /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/1789] 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 3f7c44f120..146785e522 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 f8d27e2612..05963f5576 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/1789] 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 16c7a37788..ed43f64f20 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 1d17f9ee48..0a9beb6fb8 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/1789] 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 b22d651f99..f09e4a9bb6 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 09f34d1d40..33551884d8 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 3686d4d244..14b3ad66e4 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 f512d22fc7..1171abd564 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 0000000000..09bd5a8919 --- /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/1789] 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 f09e4a9bb6..6871211346 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/1789] 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 a0049b55fc..9e981de789 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/1789] 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 e17c06a69d..0dc5ddabe0 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 0000000000..3e89361028 --- /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/1789] 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 0000000000..f20bf085b9 --- /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 0dc5ddabe0..9e97e8bc4a 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 0000000000..bd7e430d66 --- /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 0000000000..c7a5e26247 --- /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 33551884d8..ee0bd80321 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 0000000000..e0854ad0d7 --- /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/1789] 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 ffc6aa26d5..2838f2cbd9 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 0000000000..04e0bc2a1e --- /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 0000000000..355ce896f6 --- /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/1789] 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 fd243efb16..f02e5f51d2 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 afd5224ae0..82e09e0bd3 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 7d3b008d90..0643363ef1 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 0000000000..7af6660da4 --- /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/1789] 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 e121521cce..7ce4bd47b2 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 9fd242603f..f8a221dffe 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/1789] 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 d7f2c1f8ec..2b9537b98e 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 2dc82fe7aa..652fed707d 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 2b2b96c00f..1f2e534a45 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 c35966b09f..a47624e8e8 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 0000000000..6b78441412 --- /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/1789] 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 cb4dd6b858..03c2109d86 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 0000000000..5421bbc1de --- /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/1789] 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 b4d73589a5..4e9d20c150 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/1789] 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 4e9d20c150..a112f22235 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/1789] 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 0000000000..5d2dd308c0 --- /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/1789] [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 c209e8ffb0..6baecd8e10 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 ca290a17c4..fd9ddda81d 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 f02e5f51d2..5c349ce624 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 d255aa8c15..35a3ecb8ea 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 4b974bbe1d..b382891275 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 8cf3d88c19..e10e74a53e 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 03d938a7e4..b109b13f91 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 7e70566044..837c3fb890 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 81a836f620..9d4ac3d682 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 880a3c8c21..01c310459b 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 51bc6c4b2d..5d320104a7 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 1c4436ca46..a91974e466 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 c4000b9aff..fe6048dbb9 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 20ca925c1c..5f6c0d710e 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 a571338e18..42c5fe8505 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 c523fde243..a8404ef99f 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 2d52ee99e1..5bf3d9df9b 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 bdff31b842..bc00486cbf 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 2c7cad49be..09606087f1 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 70d08b9098..38592e632e 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 fb3e577d38..df49f7cb16 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 934aece46e..a9134026ea 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 8cf678ae56..af5f0e55b7 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 3aa4bf0810..fe386028a3 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 29f63a6bf2..206e7acc45 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 4e2acbd198..1ba7c4855f 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/1789] 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 34b26c6c18..b0efcd648a 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 7f60d04bfc..2d66f11a38 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 7670f1962f..5b508c2e07 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/1789] 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 5c349ce624..f05f7e4358 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/1789] 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 f05f7e4358..5c349ce624 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/1789] [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 6baecd8e10..3516ec8d6b 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 6d0bccf719..06974beea7 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/1789] 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 06974beea7..6de895a04b 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 ca8d1e7458..09f53137c9 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 517c41b909..77653e1f1a 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 9ea9c07342..cf823a82bf 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 4672fd1a96..8f2e5d096c 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 99bba5ea9b..88d5ee4122 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 fb3a9dd5b1..afc4e3f9ca 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 c2b21e92c4..122160c7fa 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 f2136604c3..7703524f47 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/1789] 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 e9308cfb77..5fd5381601 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/1789] [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 3516ec8d6b..99bd896620 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 09f53137c9..3bd5080928 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 afc4e3f9ca..2a44edc546 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/1789] 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 a112f22235..f4281c48e4 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 89e3c45ee7..ac41019e2b 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 55926064a2..8b1db512f1 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 2f8a5a47b0..5a9f17e0ab 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 b3085db0c6..ccb8188933 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 bbb00f122c..d5a1480199 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/1789] 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 8b1db512f1..4ea6fb9cd3 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 5c8dad2f23..4cd4dfc0db 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/1789] [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 99bd896620..d8172940a0 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 0707a5bc6d..df8f0f993a 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/1789] 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 0bf45b07a3..73fd744fae 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/1789] 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 793fd3d6d2..85018267b1 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/1789] 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 1973f7e303..5384f86d2d 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 3faf85d131..6d4734b397 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/1789] `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 2aa2aca526..77de1aa1b7 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/1789] 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 4f0d2ae447..76b9eff371 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/1789] 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 2f21d52123..284152b9c9 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 9d414f6f2b..99f3b1866e 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/1789] 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 6ab34811e5..0ed2933856 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/1789] Upgrading note --- UPGRADING.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index d8172940a0..8e61c27788 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/1789] 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 2dd98f2951..6ba0c0b7c5 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/1789] 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 5ab51870a8..7d2d0c9bc1 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/1789] 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 e90116c57a..f40ff3900d 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/1789] Update PHPStan extensions --- composer.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.lock b/composer.lock index e11ebd0c9b..37d6db47ae 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/1789] 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 37d6db47ae..6b92d5bffb 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 7d2d0c9bc1..2161d3f4a2 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/1789] Update simple-downgrader --- composer.lock | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 6b92d5bffb..4162573cd2 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/1789] 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 30da24daf1..33b8daac2f 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 4162573cd2..90d9df7f41 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/1789] 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 0f8d05a8d8..f18941039b 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/1789] 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 f18941039b..0f8d05a8d8 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/1789] 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 7f82682dc5..485c482644 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 0000000000..5308521baf --- /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/1789] 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 fd867cbb48..b6659a93b0 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/1789] [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 8e61c27788..2e9830c098 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 4f9a5843f1..090eea5e3a 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 dae0bbb2ab..c5f05f8208 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 b004e06bbd..552b99403a 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 0d8cfd70a7..6b8a6c44b7 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 e714c93f79..b420ae29ce 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 4ea6fb9cd3..c899ccc8cc 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 b322c4b18c..6d099d1c10 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 5384f86d2d..0f5dc0d9e2 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 7e6fefdaca..7ebb938302 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 7477416b6c..f5c746a3ad 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/1789] [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 090eea5e3a..628c9412d8 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 c9f0d94ac6..ab5b182105 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 66fd32a2fd..13440a54a7 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/1789] [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 628c9412d8..e49e16733e 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 2161d3f4a2..7d16286e4f 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 3aa75ee5fb..48da3f2c48 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/1789] [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 e49e16733e..ec3add18a1 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 f4281c48e4..d7ff2a6132 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/1789] [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 ec3add18a1..d1343fa30c 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 d7ff2a6132..5e4aa61443 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/1789] [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 d1343fa30c..6e690a7ca4 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 552b99403a..38c6cb6206 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 3fc3a15666..0000000000 --- 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 c69c9dba43..2491db1ce5 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 2491db1ce5..0000000000 --- a/stubs/bleedingEdge/Countable.stub +++ /dev/null @@ -1,9 +0,0 @@ - Date: Mon, 16 Sep 2024 10:57:46 +0200 Subject: [PATCH 0281/1789] [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 6e690a7ca4..f920c123b1 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/1789] [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 2e9830c098..f31af1f3d3 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 f920c123b1..b4d4ac25c0 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 c5f05f8208..1742fadfaf 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 e145e4b530..293c06e622 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 0a86ea9d2c..0000000000 --- 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/1789] 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 03bbcb6cce..b4000d6ada 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 0000000000..b79e076e41 --- /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/1789] 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 b79e076e41..65eebc78a6 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/1789] 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 d5a1480199..817dea23f8 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 bb813f036e..275024f3a0 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 d7b9f3ae38..5cb7be683d 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 199397750d..058059038b 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 2b5facc315..6a4a1bd0ab 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 0000000000..e69de29bb2 diff --git a/tests/PHPStan/Command/relative-paths/nested/nested.neon b/tests/PHPStan/Command/relative-paths/nested/nested.neon index 6e097edf21..c004fd54b6 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 f5c746a3ad..860b779e75 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/1789] 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 b4d4ac25c0..8407fcacd1 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 0000000000..fc6642a229 --- /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 53476ec81b..5ba98544fb 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 0000000000..ea967f31ee --- /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 0000000000..8569f55ac0 --- /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 2730a29791..5446ab66db 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 0000000000..3aaa3c735b --- /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/1789] 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 4f0d2ae447..6a1d2f16b1 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/1789] 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 c02175b246..7588947625 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/1789] 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 7ce4bd47b2..1939d5df29 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 f8a221dffe..bed27023af 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/1789] 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 0cb9f9ef98..916f284505 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/1789] [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 8407fcacd1..643a7a532f 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 1742fadfaf..177fbdc689 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 38c6cb6206..b9623abaa3 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 6b8a6c44b7..f5b9ed153d 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 6de895a04b..441778bf21 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 3bd5080928..8bcb6c1af2 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 5c349ce624..327acb7cf7 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 4cd4dfc0db..1e980d2f32 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 ccfd16f32a..20df016e0c 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 6d4734b397..b4beb587bf 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 cf058b54a8..e2b44cf399 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 f80ada4ad9..472bdeb1c1 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 061e002a46..33c3cdc439 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 7d16286e4f..2df98dd219 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 a306833e8d..952254e1b9 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 e6ac71ded5..026e305361 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 c45642f0d2..77d3f55bbc 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 1c8530ec3b..fbec2c7582 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 7bca91ccd9..51d8999c2f 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 33ff1509e0..fbadbac593 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 e8e9e3a457..5f8f7d1066 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 5b177a9f68..58bb5fe1ad 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 9927cc252e..73f074aca6 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 2eaa4308b4..9f9a51cad3 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 3eba789175..3d23ca59b2 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 d898085584..d51b5314b0 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 ed43f64f20..eec795c7ee 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 63e44b3ea2..7923e9744c 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 34d58152dc..9c28bc3c69 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 745c1c2527..3d2edcc419 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 25c73249ec..4c28461391 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 57cb091e19..f5511bfc33 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/1789] 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 4194d4f4d3..a80e510da3 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 ec6abc47e7..2b8b3b4284 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/1789] 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 6ae400e802..8454435744 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/1789] 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 0f8d05a8d8..f18941039b 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/1789] 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 f18941039b..0f8d05a8d8 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/1789] 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 1939d5df29..af64be29c0 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 bed27023af..9d353e42bb 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/1789] 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 2b8b3b4284..a4bfe75364 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/1789] 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 916f284505..b45ce15acd 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/1789] 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 7ebb938302..ba314cbb67 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 860b779e75..3c2d150290 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/1789] Fix duplicate paths in composerAutoloaderProjectPaths on Windows --- bin/phpstan | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/phpstan b/bin/phpstan index bb97758ff6..8d6cd25cd5 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/1789] 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 9d353e42bb..3684cf73d2 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/1789] 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 05df87b423..209e72730d 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 ee0bd80321..ff9f5be5ae 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 0000000000..0d6922d5da --- /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 0000000000..0cc9c7b930 --- /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/1789] `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 ed43f64f20..aceaebf670 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 0000000000..c1edbba1fd --- /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/1789] 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 8f3ecc86bc..7d4441fcf9 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 184cee83b8..7b4f758f44 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 05a71dbe97..6fa0145f19 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 05d6d79f0c..d3359c6867 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 cf4df676e9..b6d9c84ee3 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 8d680e9306..5c05e8ac09 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 f107804843..2b04d48f80 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 02f3db6590..f728505cad 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 477210064a..d6fa6c6aac 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 3777b5d6e5..9c270afa44 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 80444eb335..a3c0bfab64 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 f0eb0e338c..f0d71c3e01 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 a52cc8d718..9033aa3865 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 5387a18bfc..7bbcecefd7 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 9ac004779d..df4504cb0e 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 971868f1c4..c7531c4196 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 b208766609..a35a1c740e 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 6780fb91d3..f4ea23258b 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 adbe33ac83..10a359ea4b 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 7b3682f213..5d6b001e88 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 0ce221250b..63ea467ce4 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 b6eaa9d790..2b9fbdbdac 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 68ac27fbf2..74b46b3d69 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 7eeb304c3d..abcc1e9dc8 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 cf058b54a8..2b969a42aa 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 562401245b..fde28cab1a 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 d8a3d75a45..b0d674c716 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 43ecf911a6..f8bb760dcf 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 ee616efbc5..38092ce153 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 f8a0b7d87d..f34dd4876b 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 703c2e5a87..7d05804a46 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 0643363ef1..bf67146684 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 a7c2629d16..479f5db928 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 88d9315d84..16529f3a74 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 9be90b8054..152cbaa4a4 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 3aa4bf0810..79e7dfb00e 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 7a697e6ffa..5f14946fbb 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 a93147ab83..b173c04eea 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 24080f2e00..7e6c6015ca 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 4d89a240be..4d9733a493 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 e1c99901de..2bfd60e993 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 71fce9241d..fa4a13a729 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 7843308281..f31bbe5023 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 c145cc7cfa..00175cc6fc 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 42675f9d8a..349eb489ed 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 e6f158f91a..2be9972f15 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 95ac9ed295..e0a51fc2a8 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 c945b1efea..ec6ed43e38 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/1789] 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 f02e5f51d2..9f23cf6472 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 c2b21e92c4..37fbd12ccf 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 f66d50c140..f83e19e984 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/1789] 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 03c2109d86..7ac09d1e92 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 5421bbc1de..53313ceddd 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/1789] 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 54b26ec9f8..be27dd03a0 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 6fb9c0f411..207ff22e0b 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 0517d298af..ce13d6e997 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 9310f617f1..b6ee413032 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 9336316ff9..4024445aec 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 a2fe9cf7a3..6a147e9398 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 82240f467e..babaadaae2 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 02e81f3f55..3b481b8f14 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/1789] 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 6fa0145f19..67351dc4da 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 ed7d7a8577..70dceda653 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/1789] 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 25c73249ec..8124ffe56a 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 d32544f3e6..a9fc01c947 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/1789] 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 fbdbaf2f97..fcd467fbbb 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 0a9beb6fb8..234bbb82d3 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 0000000000..6a2c272882 --- /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/1789] 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 d06f9c4923..79f1b546ea 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 f80ada4ad9..511087818d 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 ac4b6e402b..5ed39d9797 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 59a2a26b21..ae1ef84f56 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 8f2ccac689..1f2abd69b6 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 ad8a1c2a13..d710a17858 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 061e002a46..d2eaf1bc24 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 7d2d0c9bc1..384e932796 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 08ff407033..8bf38bddda 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 c45642f0d2..00d256addf 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 a21dcb7d23..5d488d319c 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 1c8530ec3b..d17122bbec 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 9db6c41cd0..bb55a6c9fa 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 bea1d953a0..bff88f730e 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 7d25897553..a68b357722 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 8deb186895..d6f332d2f2 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 60e1046d1b..727e2f8a31 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 f79dbe88a4..af4ede1b40 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 a1452bd6c3..8e6fa67cf2 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/1789] 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 d710a17858..2e9da3ae05 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 0000000000..ae1a91127b --- /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/1789] 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 384e932796..27d874deab 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 c84a6897ff..36ecae0c59 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/1789] 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 6d987f6342..967b850525 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 8f743f4dc7..f01b15ba3d 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 0000000000..c62a9e0a10 --- /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/1789] 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 00d256addf..55953f7f28 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 45a04563c9..1732c3c627 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 2d0cc64c1e..1144acd2f8 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 37fbd12ccf..ced1959085 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 12176791a3..236e2b43d0 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 6d3d0e8645..6cb97fa4de 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 3a01a2d27b..e1a79f6937 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 38b1884bf5..a1a24e380d 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 d1bca857c3..f1f2f60f40 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 6be67ffaba..29a5f83184 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 768efbc147..4d841ad783 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 ae067d82f1..239e49d53a 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 42733805c3..fbbcff38a8 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 31914d185e..0cec47c79a 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 835ba4fdcc..6d013f52e4 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 f1b742f170..70dc86d3d9 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 a087e3468b..a3d9a57554 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 f39b3e4fac..87c0be66eb 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 5a64d20967..95e11da973 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/1789] 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 f01b15ba3d..ec893f0947 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/1789] 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 af64be29c0..c30f4a3827 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 3684cf73d2..ddf91ae047 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/1789] 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 55953f7f28..1539b6d3f4 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 c769890c9e..308064e2a4 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/1789] 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 308064e2a4..cd35c77953 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/1789] 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 69efb18635..8e9050ac4e 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/1789] 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 207ff22e0b..38afaf662d 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 0000000000..2515e3dcb8 --- /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 916532a7b9..dd90432f8f 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 0000000000..9713e71a61 --- /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/1789] 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 b7962f71ae..534be0ebbb 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 e72cf25be2..a59f86e868 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 5a8d6dcfc5..527d10b68a 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 f34dd4876b..213b9df52d 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 0000000000..5c8566f788 --- /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/1789] 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 9f23cf6472..a5dc7bc6a5 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 0000000000..2394e8a175 --- /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 0000000000..ee68b2fff0 --- /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/1789] 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 1539b6d3f4..86c5ee6626 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 976071cb84..78e536a735 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/1789] [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 643a7a532f..8538dba4fe 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 177fbdc689..246fdc091a 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 88e6800df0..f2904ef641 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 b54bad125d..c5caacdd84 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 dc567692e9..69cef6f30e 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 0f15683606..9869c16c62 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 ff0a0daa9e..c7b4f688a3 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 ea09ce0cb4..3a6c6dfb4f 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 a536ce6cf5..840da52b49 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 b733df51cd..cb14b900fe 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 70c671135a..df337d9bdf 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 31407e99be..c83001b6a8 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 8e0127cf5d..f7fa3224f9 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 2daf7b7ed4..45750ac2a4 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 86420630c2..9049635db0 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 0129923ae8..38afbe6137 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 c7de4bc7bf..ba43922b08 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 81289f82f0..5804527769 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 bc7cd35acb..ee36958b19 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 3ba7d5ce61..4503a788e8 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 c7a52e8123..b3a71307ec 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 6fa6252277..e261724d7d 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 b132e3fe08..69fd7a2c2b 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 42378de1f2..d8bb82d141 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 9d3ea64034..fa8b767c4b 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 2f4b45065f..4271153e36 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 2806935226..16dbd9dfbf 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 26643e506c..bb81a1157e 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 9ece8025a0..b9ac08e1bd 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 7f3aab6ac2..44d564b8ff 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 307db4e0a9..a3ba642464 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 bcd1c9ee87..97260fb244 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 802b0e11a8..a996359363 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 d10eee8e48..4744b8f1ce 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 f4a3d5a1bb..5827cc181c 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 2a7a309e1a..6ffeadca25 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 553ab6c462..5fe8fe58f8 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 2a92e5f5a2..e13fc184fc 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 25f0facf27..e5003028bc 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 60d620474c..3298bd5bb0 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 8619497c4f..e577240cb8 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 5ba98544fb..bd993f3bca 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 98076839d1..f0350b5179 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 681d9abab1..5c84c216a6 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 deec20a074..d658d3aeb4 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 0436117153..845c826e5c 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 d46969bdf3..f13767c4ed 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 a75873ffd5..f9ca07781c 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 953ca5f982..90564ff6d2 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 9ce75af6e6..03d672afd0 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 b5bc1f8efb..5058756f1c 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 5446ab66db..150b170ca3 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 c726ab7fdb..169acc6693 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 190b83798b..625e2d615e 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 3221658bc4..dc9d57f88e 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 a70d03a6e7..5dd8aae36f 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 ddc41ed337..909472e091 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 853f053bc7..b6ea917903 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 2809d62a9a..7e5d97a44b 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 593aa13f97..ea4b27f4f8 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 0ae3edbc3c..822a048e1e 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 3748675490..d3398ed7e7 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 7f99a5ae72..eae03f029c 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 b6d394d30b..26f8969686 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 d5834f4003..f3ba064bac 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 d5e84b66e1..59eb242e7c 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 d3196aba89..fb64553a3b 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 ed68b380b1..95e94df168 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 26157f4ffb..8f3b40c7b6 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 801d2b31f4..f1c3af3cc0 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/1789] 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 e58f023c48..8c40e83826 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/1789] [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 8538dba4fe..a95e3c79e6 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 246fdc091a..119065aaf6 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 f2904ef641..cfc414dd36 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 c5caacdd84..11267d2474 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 49a3395d2e..ed36ee647e 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 fec1521768..d079e204e2 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 395a0c41c2..17c0c34828 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 14efcc7480..8238b6ead9 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/1789] [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 a95e3c79e6..579cf43f38 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 119065aaf6..e011ee0bf7 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 cfc414dd36..3593b506da 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 11267d2474..234a86f02f 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 1e980d2f32..2a491725bb 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 786c61302c..b3089b62eb 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 1012234740..74b558e8ca 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/1789] [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 579cf43f38..c106985a82 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 e011ee0bf7..44d842cb00 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 3593b506da..8953eab15b 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 234a86f02f..2381e94d6e 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 ed36ee647e..c48074c1dd 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 d079e204e2..0c904d0d3b 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 17c0c34828..62786fe77a 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 8238b6ead9..f4ab2a470d 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/1789] 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 74b558e8ca..273394c6b6 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/1789] [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 c106985a82..baf8f62323 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 44d842cb00..cbd47d326c 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 8953eab15b..48d550549f 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 2381e94d6e..a42dd03e8a 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 8bcb6c1af2..f80ae02945 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 6e88b1ab68..5814357192 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 ca350a16b3..a87e4b47e4 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 39155bdb6f..3be2818253 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/1789] 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 af4a00fca8..9f4f4c42a5 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/1789] 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 e87b7b38c8..10b814e08a 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/1789] 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 c30f4a3827..deb73479df 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 ddf91ae047..cb42d7d1e4 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/1789] [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 baf8f62323..7b7dfedae2 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 cbd47d326c..b3e0abb5c3 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 f205db23b6..dfbfc00454 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 349523c3a0..199a794a53 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 1029bcdba0..2b50d58d83 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 48d550549f..f21201c707 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 a42dd03e8a..96140b3296 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 f80ae02945..4e7f170897 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 0c5d975f2f..e866f7081b 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 73782fcf53..3b7a264656 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 5ef4db93d1..8fe57003b7 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 5814357192..802be9e599 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 a87e4b47e4..9089e0dd37 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 3be2818253..e693b1beb9 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 a12ee381e9..73a197f3a5 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 fecb8a1045..48c0c6acde 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/1789] 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 0000000000..31c201d165 --- /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/1789] [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 7b7dfedae2..1352fbce3d 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 b3e0abb5c3..c476e5106c 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 f21201c707..6cca06d109 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 96140b3296..447f4eedb6 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 4e7f170897..8ee2e80fcd 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 802be9e599..716f13bc79 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 9089e0dd37..d406cc4bf5 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 e693b1beb9..ee8c645bd0 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/1789] 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 1352fbce3d..31fb869728 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 479f5db928..840839ac9b 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 0000000000..cdbb585f9a --- /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 0000000000..0984dce665 --- /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 0000000000..c70978996c --- /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 dc9d57f88e..c6879f2dc1 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 0000000000..c0fe6b5feb --- /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 59eb242e7c..3616824c7f 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 0000000000..9e4d4e7b09 --- /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/1789] [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 31fb869728..8af379cecf 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 c476e5106c..bd47059f3c 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 6cca06d109..5fe45a5ef6 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 447f4eedb6..61294c35f5 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 8ee2e80fcd..ba87e0f164 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 716f13bc79..5aac77f772 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 d406cc4bf5..02fea01cc0 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 ee8c645bd0..0a27ee2889 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 debb459f03..0000000000 --- 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 e763557205..0000000000 --- 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/1789] [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 8af379cecf..98d047542b 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 bd47059f3c..589c51b4c2 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 5fe45a5ef6..81d10a1624 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 61294c35f5..6685779c13 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 e866f7081b..dd1461ecf4 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 1e9d6b1ba2..194a41d74c 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 cbdac548bc..6fd66f7e28 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 dd90432f8f..722dcf7747 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/1789] [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 98d047542b..ac4b73f38c 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 589c51b4c2..543fb682a3 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 1382d99ee1..efa06ccb5d 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 81d10a1624..de2f0d05a9 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 6685779c13..8b7291a6d7 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 dd1461ecf4..04223a005e 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 284152b9c9..a9b741979a 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 99f3b1866e..da41d2b685 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 6fd66f7e28..5ad57bd6c5 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 722dcf7747..abae064fb7 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/1789] [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 ac4b73f38c..4467e39234 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 543fb682a3..a4e2926ed4 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 efa06ccb5d..7a9c5c6c42 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 de2f0d05a9..1c97e2d9c2 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 8b7291a6d7..10db8d0910 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 04223a005e..dc930e386d 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 da41d2b685..01fbaff3dc 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 5ad57bd6c5..5f75cd0554 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 abae064fb7..5a8b677229 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/1789] [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 4467e39234..37f6429f1b 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 a4e2926ed4..d0b7eba94b 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 7a9c5c6c42..60f2e7e5fc 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 29234ebe71..6ff60ddf87 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 2b50d58d83..e49ad444b9 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 1c97e2d9c2..1e7fc5ed63 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 10db8d0910..f797b4608b 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 dc930e386d..e37624c68b 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 5347681a90..fdc99bed07 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 a17ef3d200..6e5611b0f8 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 3942cc1b40..9fcea27964 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 ef9ce469b5..201b5d3dc2 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 e8c07ca171..7455afc8cc 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 fb443854df..a598bbd9df 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 58ddda39a4..05f8fe03ff 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 b1a1bb39ca..acaf1974b0 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 a16f6ac23f..f23e120458 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 08d3bb1c02..dbc7906da5 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 a409df4391..5b603724ec 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 657dc19df2..db80402633 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 06957666f5..46e872ec74 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 05963f5576..4fc2eb30e8 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 017065a4a8..034d8044b8 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 3a9d430ec0..1c46e94d0c 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 196e663c7b..4656dd37c6 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 359d220880..4ca35ed5e0 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/1789] [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 37f6429f1b..c2df639285 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 199a794a53..327f98e113 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 1e7fc5ed63..cb61959d66 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 7b329d01a4..8f78c9023b 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 5c05e8ac09..013e6b4b81 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 f728505cad..b991f45981 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 840da52b49..d37f09084b 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 f7fa3224f9..3862059b02 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 b0d674c716..e84e4956c7 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 38092ce153..b5d52ba9a0 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/1789] [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 c2df639285..2027c9081c 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 60f2e7e5fc..1d5325aefa 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 6f3382d829..8283936e73 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 5dd8aae36f..63c757ecc8 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/1789] [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 2027c9081c..64b626d8af 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 6ff60ddf87..715f1089a8 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 77653e1f1a..e44b2178d5 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 cf823a82bf..6600cce4ad 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 625e2d615e..24ae397dbb 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 909472e091..afd0611e89 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/1789] [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 f31af1f3d3..07238f106c 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 64b626d8af..bcda7deb8c 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 d0b7eba94b..b910eaa88e 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 e49ad444b9..ec0c815769 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 cb61959d66..a5fdd2eb72 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 ae22e5ccdc..c33dc1edf2 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 f797b4608b..3c8b147a88 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 c899ccc8cc..bd25174977 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 d6c55e62ec..50e081b0e5 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 7455afc8cc..9bf5516553 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 a598bbd9df..1dec5922bf 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 05f8fe03ff..d78a5c795e 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 7766e03bd8..8a8489dc5e 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 74f70ca72d..6c048e0674 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 7839c99123..3a2e328b99 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 acaf1974b0..101500b183 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 f23e120458..87dbaaf1f9 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 dbc7906da5..2fc73704ee 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 7b36d89e90..43dc3e29f9 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 3e6ff6c953..e4b92673e5 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 76b4342abe..50212df3db 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 89b6302e8a..e3cccffa5f 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 73a197f3a5..6eb78628d2 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 37cd3ffb81..0a93340e1f 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 48c0c6acde..a7ce5312ca 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 0762900901..742d3f0287 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 373e46494a..9f3af68ae6 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 9d4dc0bb3e..e432a8b7a9 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 bd92fe752e..a334333851 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/1789] [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 07238f106c..752970842b 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 ec0c815769..25214e97dd 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 a5fdd2eb72..801e3fe8b6 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 c33dc1edf2..07390c7b8f 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 3c8b147a88..0919020939 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 bd25174977..f380cfdbd0 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 201b5d3dc2..8dc3830cad 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 50e081b0e5..dd0d451478 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 9bf5516553..47b81f1ea1 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 1dec5922bf..76dc96f81c 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 d78a5c795e..a84377316b 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 8a8489dc5e..ae7ff38ec5 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 6c048e0674..fce38f5d98 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 3a2e328b99..5317938620 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 101500b183..e05283e71b 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 87dbaaf1f9..9465723963 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 2fc73704ee..c94636b5e1 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 43dc3e29f9..4ec02315e1 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 e4b92673e5..1266a5a553 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 50212df3db..f45a1b894d 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 e3cccffa5f..13e745a3d1 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 6eb78628d2..c88fb550da 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 0a93340e1f..2b64aba5ba 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 4fc2eb30e8..867cff2e50 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 034d8044b8..0978f6c7c5 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 1c46e94d0c..8d7a275278 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 4656dd37c6..c62ce6fc24 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 a7ce5312ca..f19534b281 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 742d3f0287..87a2f4c46b 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 9f3af68ae6..cc74d519e9 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 e432a8b7a9..e5152569d1 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 a334333851..2338b63afb 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/1789] [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 752970842b..263be4f14e 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 801e3fe8b6..d9d8d98dc5 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 0919020939..f91e092b72 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 f380cfdbd0..0fe6f1c7f2 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 bfc45d6c3e..4fb9f47156 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 c15b426f6e..0bdae44c20 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 cfa21959b9..a5ed7689b6 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 a4921c9201..724380955e 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/1789] 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 5e4aa61443..a81115e108 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/1789] [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 263be4f14e..e4d362aa37 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 d9d8d98dc5..99cebcaa70 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 f91e092b72..e0a7bb3a98 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/1789] [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 e4d362aa37..aa9b5b5bbe 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 99cebcaa70..f354735cf7 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 e0a7bb3a98..908950b191 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 a7cb8997d8..0c4bee2f63 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 817dea23f8..ab4b4b793a 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 834bcfd001..eb1330b4fc 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/1789] [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 aa9b5b5bbe..cb182fbd3d 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 f354735cf7..8e53e03343 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 908950b191..c5d8a0b821 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 69cef6f30e..edb70aafe7 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 c48074c1dd..a696c24777 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 fdacebc9ec..83c943f917 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 0c904d0d3b..073bc6baef 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 0fe6f1c7f2..c3cfbbedfd 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 f4ab2a470d..b62d258607 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/1789] 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 287df68634..ee96f40466 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/1789] 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 f2a9af9989..8ccf92e3df 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 ee96f40466..6bd3e1dfd7 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 0000000000..a4abe2dafc --- /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/1789] 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 b62d258607..c5d0a651a6 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 0c4bee2f63..e84ac78e28 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/1789] [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 bcda7deb8c..d84b7cf2e8 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 b910eaa88e..6c988343b3 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 2b9537b98e..c9f98dbe47 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 11dd4fa773..92f41e9db0 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 1b234d2280..8b5c5b9f42 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 82e9b720a8..92f41e9db0 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/1789] 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 8e53e03343..a737795700 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/1789] 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 fbe53cbc6a..be1558fe7b 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/1789] [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 d84b7cf2e8..882c1decbd 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 6c988343b3..bac0b4dedf 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 a737795700..e28fbc692a 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 c5d8a0b821..90aca4d582 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 e37624c68b..96922b4c09 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/1789] [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 882c1decbd..d692b7a4c9 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 bac0b4dedf..b872cc3b98 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 327f98e113..f28b2e2f9a 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 e28fbc692a..079f1b457d 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 90aca4d582..21e82bdaa0 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/1789] [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 d692b7a4c9..6941b58e5a 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 b872cc3b98..5eb0bf1979 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 f28b2e2f9a..cc3cdff418 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 079f1b457d..cb5855c70e 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 21e82bdaa0..be829608c9 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 2e246941b7..abc5200a71 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 a12768aa0f..2e082297d1 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/1789] 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 5eb0bf1979..6ee6d1fe29 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 cb5855c70e..ae1b954687 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 be829608c9..efeb421ebc 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/1789] [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 6941b58e5a..991f721fda 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 6ee6d1fe29..0b6d0ab3e3 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 1d5325aefa..a7cd743caf 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 ae1b954687..65ba96b61c 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 efeb421ebc..9f85497e62 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/1789] 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 0b6d0ab3e3..cffea53253 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 65ba96b61c..967c875dc4 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 9f85497e62..b0b46521e6 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/1789] [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 cb182fbd3d..18861737b0 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 991f721fda..c58e4ee853 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 cffea53253..fcc1ec9d55 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 a7cd743caf..1f1aa09e62 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 967c875dc4..8b60279ccc 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 b0b46521e6..fc1d1ea225 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 a1f27a33e2..7ad74631b9 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 13505dbce7..3b97a1b99a 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 a6bee51214..b7ee628d37 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 2e5388d2b1..c7fe99bc18 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 569d7747eb..0000000000 --- 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 efb020baea..0000000000 --- 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/1789] [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 c58e4ee853..594e85adb0 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 fcc1ec9d55..82bb13d05f 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 cc3cdff418..27c09f7ab7 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 8b60279ccc..966b903b3c 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 fc1d1ea225..55d94d52ff 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/1789] 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 82bb13d05f..0e5c07251e 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 966b903b3c..3c26e4825f 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 55d94d52ff..2a7bae6797 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/1789] [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 594e85adb0..51c219357f 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 0e5c07251e..c695c525d1 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 3c26e4825f..c4892a4de7 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 2a7bae6797..ce34892968 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 39a4da06bd..45574a089a 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 1f715dad2c..a6ab212328 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 e84e4956c7..1b66a5898e 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 980be0e207..5fb982f548 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 b5d52ba9a0..4db5d7167a 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 7d05804a46..4ddf9941e8 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 bf67146684..4bac3a314f 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 840839ac9b..80ee3c0763 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 7a9e4e59af..bee9669510 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 152cbaa4a4..47fb5d60f6 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 54564bf9ea..4d7eedcf84 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 5fb28b96ba..cfaf5fce01 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 b173c04eea..b3c7227ee1 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 7e6c6015ca..4eab90f017 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 b727ba76e2..4674b642a7 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 fa4a13a729..2169597e68 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 f31bbe5023..4459045cc0 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 00175cc6fc..bd3cc00a4f 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 349eb489ed..df5ce1d3c8 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 2be9972f15..83f53c071a 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/1789] 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 41c947e937..a656898b2a 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 0000000000..3a848d6769 --- /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/1789] [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 51c219357f..80784d2042 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 c695c525d1..4d893731f5 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 c4892a4de7..e6ed12ba2f 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 ce34892968..68102c39ab 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 ca13ca07a6..376a7109e6 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 867cff2e50..a7bd6c6c7a 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 0978f6c7c5..af69ef5ea9 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 8d7a275278..c5c92bb097 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 c62ce6fc24..7e1ee17d34 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/1789] [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 80784d2042..58a26b37cf 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 4d893731f5..8ec2f5e19e 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 e6ed12ba2f..dcc8b5c61a 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 68102c39ab..7b991aedb9 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 376a7109e6..95170eecb4 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 a7bd6c6c7a..b36d872851 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 af69ef5ea9..3bc3ac59e6 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 c5c92bb097..ee7fd32fd9 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 7e1ee17d34..9eab4b213e 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/1789] Upgrading note --- UPGRADING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 18861737b0..e6ccef2f83 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/1789] [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 58a26b37cf..7bd3f55652 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 8ec2f5e19e..3356f15719 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 1f1aa09e62..be485cdbc3 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 dcc8b5c61a..3de3cefcab 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 7b991aedb9..07fc7a4084 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/1789] [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 e6ccef2f83..9a0e770952 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 7bd3f55652..f6897df85e 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 3356f15719..7fbd8c23b3 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 be485cdbc3..0e00bc5e92 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 3de3cefcab..66ea8a4a67 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 07fc7a4084..a4373609b7 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 1ae24aa0ca..c78d682e11 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 6be9f18d4b..e12f8aaf14 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/1789] [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 f6897df85e..7fee5b125c 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 7fbd8c23b3..5a4efe4678 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 66ea8a4a67..637fc05249 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 a4373609b7..074b8bd080 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/1789] [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 7fee5b125c..24792df71a 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 5a4efe4678..3889cfac9e 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 cdc7638cde..2f2e4feaca 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 637fc05249..1d3f9c31aa 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 074b8bd080..5a53e3e544 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/1789] 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 1d3f9c31aa..5a0f69d1e7 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 5a53e3e544..ade1f92f34 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/1789] [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 24792df71a..2796a14ccf 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 3889cfac9e..82b2a09848 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 dfbfc00454..089569684c 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 5a0f69d1e7..b5f6c75be7 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 ade1f92f34..f403ca849c 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/1789] [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 2796a14ccf..fe5a7ae058 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 82b2a09848..5b911974f4 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 b5f6c75be7..5d60538a42 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 f403ca849c..96adcd6578 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 412dd928ba..9f42773237 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 a5ed7689b6..c5ef275d1f 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 c7b4f688a3..a49a650286 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 e261724d7d..aac5cbda3c 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 69fd7a2c2b..d5787f8344 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 fa8b767c4b..4907d1d7ec 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 4271153e36..657c47bd75 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 bb81a1157e..52428ced2b 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 44d564b8ff..af9266b7e5 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 97260fb244..31a92a4f92 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 22dfd46eb8..c150f1890e 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 4744b8f1ce..f4eb4c2c6e 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 5827cc181c..dbab699a2b 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 5fe8fe58f8..6d028b1b96 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 3298bd5bb0..842d0513a1 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 85f5aa6e8d..8eeba69aab 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 f13767c4ed..56e260b8d2 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 03d672afd0..ef5ca25aef 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 26f8969686..02b75d1007 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/1789] 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 fe5a7ae058..a72024427b 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/1789] [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 a72024427b..e40d87985a 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 5b911974f4..1fd98bf92a 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 715f1089a8..5fd37f5fd4 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 5d60538a42..79392f5d1a 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 96adcd6578..0081f682df 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 87784c71d6..72e61ffdb6 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 6083de943d..155c0d6ea4 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/1789] 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 62786fe77a..0e2ead8dde 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 822a048e1e..87861e276e 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/1789] 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 87861e276e..acbfca290c 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/1789] [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 e40d87985a..668dd218a3 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 1fd98bf92a..1154241b44 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 79392f5d1a..fef4f694c9 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 0081f682df..25cc07815b 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 1a07cb30c7..89433d32ca 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 6fa229669f..150eb99d7e 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 2c22dfd35a..a3e1ed68a2 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 302858a4e5..2b32877d52 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/1789] [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 668dd218a3..d9ad8796e9 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 1154241b44..2a6accd9dc 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 2f2e4feaca..3fa1bb8e29 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 fef4f694c9..9038685e3a 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 25cc07815b..8a22e0fa82 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/1789] [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 d9ad8796e9..d98795d5f5 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 2a6accd9dc..0ad75d4718 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 5fd37f5fd4..7863272da9 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 9038685e3a..76b8e8e814 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 8a22e0fa82..a9f23039ef 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/1789] [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 d98795d5f5..53269961ab 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 0ad75d4718..35f6a99226 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 0e00bc5e92..711beeff40 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 76b8e8e814..a6871cf0b0 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 a9f23039ef..5a960314b5 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/1789] [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 53269961ab..a1e03d841a 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 35f6a99226..ab60f6996a 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 27c09f7ab7..21621869e5 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 a6871cf0b0..096d92c518 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 5a960314b5..be8d4959b9 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/1789] [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 a1e03d841a..19be2cdc9c 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 ab60f6996a..37de1e3e7e 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 7863272da9..f75e01dfde 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 096d92c518..7738f1ffa6 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 be8d4959b9..e4f663d442 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/1789] [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 19be2cdc9c..76dd765878 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 37de1e3e7e..32fb150921 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 711beeff40..d9fdeea93c 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 7738f1ffa6..bc7e8a3aa0 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 e4f663d442..0582652892 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/1789] [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 76dd765878..49420a6753 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 32fb150921..ae30dc43d3 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 d9fdeea93c..731f86cb11 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 bc7e8a3aa0..02795237fc 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 0582652892..1d2af1cb84 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/1789] [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 49420a6753..6eab345888 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 ae30dc43d3..1c68c2c5ab 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 731f86cb11..691f468809 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 02795237fc..6466d2dfd2 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 1d2af1cb84..3135bb2353 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/1789] [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 6eab345888..100086e449 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 1c68c2c5ab..eb63f79d85 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 691f468809..d62a5e4ce9 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 6466d2dfd2..48adf0b874 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 3135bb2353..12d68d952e 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/1789] [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 100086e449..c269678b38 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 eb63f79d85..90ed6da046 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 48adf0b874..b50daa7879 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 12d68d952e..a6278e72e2 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 5f6c0d710e..e31033185e 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/1789] [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 c269678b38..d5b46b03d5 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 90ed6da046..18fc70d92d 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 21621869e5..36cc5448cb 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 b50daa7879..339b64e24a 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 a6278e72e2..c9027f11fd 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 0c74e65a02..58cf807050 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 9e9588d219..019e7bf59f 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/1789] [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 d5b46b03d5..8f14a5e937 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 18fc70d92d..7e102ef694 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 36cc5448cb..52ff204352 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 339b64e24a..01a3bf1206 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 c9027f11fd..dcf4230ff9 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 edb70aafe7..287fdbae03 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 81cfb1f471..d204ca9c64 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 5d6b001e88..0000000000 --- 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 63ea467ce4..0000000000 --- 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 4674b642a7..8d9cf3443e 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 4459045cc0..0000000000 --- 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 bd3cc00a4f..0000000000 --- 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 be05325779..0000000000 --- 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/1789] [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 8f14a5e937..cc93b6af1b 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 7e102ef694..578124828b 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 01a3bf1206..ac3f1d096c 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 dcf4230ff9..97ac234f8d 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/1789] 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 4acec2e4a1..997e53f86f 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 8dca547099..7479e1b1b1 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/1789] 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 9a0e770952..658f366c00 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 52ff204352..ed64b65aec 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 ac3f1d096c..a6be092dc4 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 97ac234f8d..d5a31f5b0e 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 b6d9c84ee3..a4b3cfe70c 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 d6fa6c6aac..09961335e7 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 9033aa3865..690307e6fc 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 7bbcecefd7..4b79d98acb 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 df4504cb0e..e4b3721538 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 d204ca9c64..6edef9747b 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 f4ea23258b..637ba4c50b 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 fde28cab1a..f8228f04f6 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 213b9df52d..cbd60c68ed 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 bee9669510..95839c42cc 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 47fb5d60f6..03de668ca6 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 4d7eedcf84..736e912ab7 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 cfaf5fce01..2741c67864 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 b3c7227ee1..3133e3aa96 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 8d9cf3443e..072475e3e7 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 4d184029d7..f30f842ca2 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/1789] [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 cc93b6af1b..274dae47b3 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 578124828b..54bad4da40 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 ed64b65aec..714843e781 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 a6be092dc4..c9fd627837 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 d5a31f5b0e..68ebb600e3 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 9271ef2782..dc3167a38d 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/1789] [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 658f366c00..2ac65c3d17 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 c9fd627837..f901eb6dfe 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 68ebb600e3..9871903109 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/1789] 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 f8228f04f6..a7c21443f1 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 1b66a5898e..566c651fd4 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 5fb982f548..ce5d1615f4 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 4db5d7167a..ca46233349 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 cbd60c68ed..e0bbf466ab 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 4ddf9941e8..38f3237a45 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 4bac3a314f..e734b8ea1f 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 80ee3c0763..1f03a122bd 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 95839c42cc..5480eb394c 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 03de668ca6..67245ebaba 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 736e912ab7..b81646c023 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 2741c67864..eceaff15f9 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 3133e3aa96..1cdbc1a7ad 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 4eab90f017..c191856047 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 072475e3e7..365b9dff9c 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 f30f842ca2..0d9fb746aa 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 2169597e68..e1e7474e17 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 df5ce1d3c8..4d65f02d2b 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 83f53c071a..4a377f1855 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/1789] 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 0f8d05a8d8..f18941039b 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/1789] 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 f18941039b..0f8d05a8d8 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/1789] [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 274dae47b3..bd23ea35b3 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 54bad4da40..fa60d6e22a 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 d62a5e4ce9..13147b6c45 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 f901eb6dfe..e8bad648c4 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 9871903109..31e5cad0e0 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/1789] [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 bd23ea35b3..6da15f7259 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 fa60d6e22a..555389c6e6 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 3fa1bb8e29..a8aa14b986 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 e8bad648c4..5104a7e040 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 31e5cad0e0..351862a0cd 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/1789] [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 6da15f7259..803b7d8f42 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 555389c6e6..694eba9796 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 a8aa14b986..0cbccea5d6 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 5104a7e040..aa8fcc3a63 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 351862a0cd..652bda42b0 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/1789] [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 803b7d8f42..29e9d612b4 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 694eba9796..89a624594a 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 13147b6c45..6dd5e4c7ca 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 f75e01dfde..b9fbf7d023 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 089569684c..250d545f15 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 aa8fcc3a63..5f0d9fd1ef 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 652bda42b0..51133df707 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 4ddff55f11..f222bd9945 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 97870f3572..0708ec3095 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/1789] [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 29e9d612b4..34f9d49201 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 89a624594a..8e06b22fda 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 b9fbf7d023..b3507a4a46 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 714843e781..5636417046 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 5f0d9fd1ef..9deb65b892 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 51133df707..cbdba229a4 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 c50d21d878..f214edf960 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 29e99526e2..c7c0e8f89a 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/1789] 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 0f8d05a8d8..f18941039b 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/1789] Upgrading note --- UPGRADING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 2ac65c3d17..640d57af1f 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/1789] 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 997e53f86f..96460dab5a 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 7479e1b1b1..bea3c5e356 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/1789] 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 b3507a4a46..2d547cb94e 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 9deb65b892..46424fd414 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 cbdba229a4..7307fc8571 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 1dba6ed6d2..0000000000 --- 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 fa747d6a2b..0000000000 --- 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 8b0f957e85..0000000000 --- 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 065a5785bf..0000000000 --- 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 2214d45b33..0000000000 --- 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 f5198a5089..0000000000 --- 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/1789] 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 f18941039b..0f8d05a8d8 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/1789] 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 a413c9bae1..64131446d9 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/1789] Update PHPStan extensions --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 96460dab5a..c17a33a1e1 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/1789] 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 34f9d49201..7469ed558c 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/1789] 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 0000000000..dd57bdf13f --- /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/1789] 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 46424fd414..05aab74b88 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 8dc3830cad..1cedcda8be 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 81b4296100..204da1481a 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 45f5d7634e..ee86187c8c 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 0000000000..66b17f1111 --- /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 0000000000..ed3d9bd3bf --- /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/1789] 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 6dd5e4c7ca..d8b1b775cc 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 dfe2a48d4b..0000000000 --- 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 df14e33493..0000000000 --- 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 4a08a799a8..0000000000 --- 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/1789] 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 7469ed558c..d9fe72d6c9 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/1789] Fix typo --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 640d57af1f..a6b527bc54 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/1789] Fix build --- phpstan-baseline.neon | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 287fdbae03..dbc99984e0 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/1789] 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 deb73479df..3b27d11751 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 cb42d7d1e4..12c507d0e8 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/1789] 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 dbc99984e0..9131efff54 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 9869c16c62..1fe931aca5 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 7e5d97a44b..164aefaafe 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 f83bc6ef85..f755c42eac 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/1789] 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 b4000d6ada..9780b2c69a 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 0000000000..65e7fd1691 --- /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/1789] 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 aa6291fdfe..b6df87becb 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 0000000000..c7c6ae5a96 --- /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 72c9d4bcda..1c006ab6dc 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/1789] 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 a6b527bc54..14b029acc9 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 0e2ead8dde..2c9ef02f75 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 8c40e83826..9484fc52a7 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 60219c3b6d..83315b6e0c 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 b4beb587bf..fdb3344d69 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 ecca6e7d09..e35c21a3ed 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 ea9b2f4660..775a4eb50f 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 db5328d0d2..cbebe5b48b 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 39b259ee37..09ddd2741e 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 42d8f4afc2..e0bc8c3340 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 534be0ebbb..d5680a59ac 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 8bf38bddda..13704c9a5d 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 a59f86e868..01870ed2da 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 2b3b4a45c5..c6efe96950 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 527d10b68a..7452cd3c83 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 bff88f730e..ff3d015f36 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 e40b72fad4..6adf571d54 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 e5c6d2c891..6761274cf1 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 727e2f8a31..47446d0dad 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 af4ede1b40..839bad3817 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 a6a68a9b5e..f2b13d25c2 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 c7b42d7e6e..c5211fd650 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 4b9ab4e23c..40c0526e25 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/1789] 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 2b9537b98e..b79549a45d 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/1789] 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 4688917e1f..26cc34d860 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 4623719b4d..0081826133 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/1789] `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 a5dc7bc6a5..fb8b5985e7 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 0bc7c1f4c4..2b15a4eb65 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 2394e8a175..e637669483 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/1789] 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 86c5ee6626..865611056f 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 31c201d165..59fa2afa13 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/1789] 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 3b27d11751..86914b4b26 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 12c507d0e8..366cbe5c06 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/1789] 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 7ae801bfd9..17ba5ade7b 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 12f97e63b5..0d43930de3 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/1789] 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 79f1b546ea..b7c18db8d9 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 b79549a45d..80c57788d6 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 4eae1e59f9..b275c464eb 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 c0fa6c5585..06098d7938 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 1eec9736d1..8b015c91ab 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 1ae24aa0ca..225e65b000 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 f4ea23258b..c703e93cb2 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 511087818d..d32cc8882c 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 dd4af579ac..110fbea58c 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 0000000000..195a807dbc --- /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 16566ee530..b911c21fbb 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 dacccd3e31..5703420e9f 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 9d37625612..9cb274782d 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 5ed39d9797..2194a10cef 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 ae1ef84f56..853645c91a 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 1f2abd69b6..293e1f111f 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 2e9da3ae05..fa64240e89 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 d2eaf1bc24..4543e36b77 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 ce30d8983c..82b3645a27 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 802d17e162..eaca9d4572 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 967b850525..e55847aae0 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 fcd467fbbb..382911e69d 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 2111d4d912..0ffa96e002 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 8bf38bddda..22a4d811e4 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 f36cab2e7f..6ef8ff5ee3 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 294b7d1d2b..7f48131262 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 865611056f..f72e8f3d76 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 5d488d319c..1523562cab 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 a59f86e868..ab50c2040e 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 1732c3c627..b756e3f77d 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 9927cc252e..ccad08cefb 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 bb29690bd6..d29e20a7cc 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 39355b579e..149caff081 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 f40ff3900d..4364f2374f 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 bb55a6c9fa..e0d2924951 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 cc114d5c34..00a9141233 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 5fe9ae444a..9fcd1e1895 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 bff88f730e..fb03a4d170 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 0c37e439c7..8ca9c69c29 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 727e2f8a31..398529994e 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 af4ede1b40..08b8d2c186 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 485c482644..f91ea5cb45 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 d9724fc64d..52b66c154f 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 8cbd1076ba..add4ffca45 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 234bbb82d3..0cb675d14b 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 a4bfe75364..2816b5ce52 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 99bba5ea9b..fe0644cd30 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 0000000000..efdd404424 --- /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 0000000000..4fa4539356 --- /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 1544279ea2..1d75efdcbe 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 46bc69565f..8fbed765a9 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 0b48d36ebc..fcdca30b44 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 06881808e8..b97b117179 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 ad8e375e58..a1d7279252 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 0000000000..3aff9f3389 --- /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 0000000000..b2a2ff43fa --- /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 70dc86d3d9..9c3296c3d8 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 c1f85c4df7..42dacb886f 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 78e536a735..19a5d6b96a 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 f8c6dc40b7..4127dd5870 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 df4883845a..3f0c76f250 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 2bfd60e993..e6ff6be2d7 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 0000000000..772939626a --- /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 0000000000..40c475e9e5 --- /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 976917737b..3845352fd6 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 e083852496..2e2bcf976c 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 aae9faa0bd..7c9bcc0722 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 95e11da973..55dc30542b 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 592271bdda..eebdb08014 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/1789] 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 7ac09d1e92..589fd947d2 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 6871211346..c9ae804546 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 53ceaa3443..0c1682209d 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 0b065f8a25..4ab53d25c3 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 0103f58059..96218713d3 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 7882c3473e..db7c28afff 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 4a02e64989..8d4714f423 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/1789] 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 8e2429016c..86ab366632 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/1789] 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 86ab366632..538762ba0d 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 40839309e5..2dca4eb85e 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 6dafe5d35b..7283f333d3 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 f07b2aac37..1ec55619b6 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 e50021d7ed..327c0a7be4 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 7a64f9e7eb..af9d403b34 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 73782fcf53..a7519de7a5 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 1546a63219..d49e7f9aa9 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 8d4714f423..9fdd33f14a 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 89a5d1fff1..b73b5a3509 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 25ddeef8bd..c6283e8bb5 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 d97c3eb76b..c8475e23a8 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 e73c28da76..65e0f7bca3 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 4590950baa..44543983dc 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 409b32d65f..d8814af601 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 2f21d52123..d8bb7f1f4f 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 1e9d6b1ba2..b0965e55bc 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 5ef4db93d1..32d64a68ad 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 e0e6685ae9..e48ed2d785 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 9d414f6f2b..29a936b35a 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 00753a620c..9f851ce9c6 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 a1f16bbd07..9a3e64e3ac 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 84f6db5239..5bb4714521 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 d07066b4cc..fdef4e9396 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 2355503a93..622a093e0b 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 30685b4d6e..5dd972f709 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 6c62fea688..8e2c1f57e8 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 4f4aedec6d..217d8576cd 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 b404ff3464..9c098e27b2 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 0c74e65a02..ebb22df8ae 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 0d4487d217..e24a938064 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 177079ac6c..b87573b8ff 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/1789] 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 589fd947d2..135a0deae9 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/1789] 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 7ca1a0a70b..0fc017e67f 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 72b26d3dd5..925873293d 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 eda7d4df47..65d82ccd7b 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 d6cb144506..dc27fe349b 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 fe28eb34ca..73bdfa483f 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 acd5ab309b..c2c23e4155 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 479adf4c43..084ee527af 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 fa6fb0a43a..83a8ff826d 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 02824d3c9c..aedf0bca2a 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/1789] 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 57a80a5a2e..f9bfcf314b 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/1789] 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 4d14c56ced..3ec43fd80f 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/1789] 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 135a0deae9..086a09c3c2 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 0486d11048..b4065332ce 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 9527e526b7..d980be7864 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 69d7a7a1a6..b81e30e846 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 7f81cd1363..ca67c3c2e2 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 79e85fda5b..ba4faeded3 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 14f7061aa4..7cb583f1dc 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 bdd5ed8d63..e09ceb27ed 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 2dd98f2951..c2a61a0131 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 425f75edd6..d588cea558 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 0f47e2c746..be5d97660f 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 ec9f1b3b9d..ff5fd5f9a5 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 e343d801b1..dc8f74ecea 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 538762ba0d..96ba2f6be1 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 a52f3829f8..1f3ffee131 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 5a75f85a8a..390ebc886a 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 be769e05a0..a39fe6c67b 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 69863e8666..477cbdea74 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 c447875580..65c3b8b152 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 3b2598c368..3808304444 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 78f31cdce6..12d263b0e6 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 b0965e55bc..4cd7fd8277 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 b756e3f77d..31a9613e91 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 c98129fc99..b237cdd33a 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 8d4f675a66..b7f4e23bf0 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 dfdb480fb8..36b4402d9e 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 fa00afc63f..d798e65b66 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 f6c511df33..5c5248d38c 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/1789] Note about removing `ParametersAcceptorSelector::selectSingle()` --- UPGRADING.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 14b029acc9..d28c6aed08 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/1789] 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 3ec43fd80f..619ee2aa81 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/1789] 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 a696c24777..6b66961f4f 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 83c943f917..6d8608ec18 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 073bc6baef..c2b2069ac5 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 7012690dd9..9572fa7580 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/1789] Update phpstan-deprecation-rules --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index f131aa7efb..b72f00167d 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/1789] 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 619ee2aa81..f9f66c7531 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 446e478cd0..dd72c4525e 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 d8977504d2..79fb96b28a 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 ec450207c7..f0f5bdd9ab 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/1789] 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 c703e93cb2..0db119501f 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 e6ff6be2d7..e50ab41805 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 9719e9c133..3e05dd8b83 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/1789] 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 c2b2069ac5..32cfc6f843 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/1789] 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 83a8ff826d..eb02e6e436 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/1789] 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 c76ca0ebca..7e5fad5dda 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 270658e7ef..0000000000 --- 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/1789] 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 652fed707d..bcf195b086 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 0000000000..e5632e293d --- /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/1789] 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 fd98d36ae4..92b0ec286d 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 22c9714168..632fea85f0 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 ff63036d9b..93bf8949d9 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 0000000000..79633d7538 --- /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/1789] 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 18a66da75c..c80c10d9c0 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 0cb675d14b..9860a71f59 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 ef291855ed..a38116b682 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 0000000000..c9546ba2d0 --- /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/1789] 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 bcf195b086..853222642c 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 0000000000..ba95e975da --- /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/1789] 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 534be0ebbb..8a9414fbee 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 26cc34d860..4e00f1d8ec 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 0081826133..ad53206b25 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 0d43930de3..e726c77d75 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 0000000000..494c135d95 --- /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 b310ffe1a5..afda5d2229 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/1789] 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 1f4fc69bd3..05684938b8 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/1789] 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 2a3d43c9c0..d5c7450d19 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 9860a71f59..430b0e02f8 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 0000000000..1a6f7e683e --- /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/1789] 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 d5c7450d19..19d6c20b26 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/1789] 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 0fc017e67f..8e43bd2d47 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/1789] 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 d28c6aed08..6dc6ec47e3 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 d92f983a3e..1ad85c60be 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 d4dc6fe133..20335325a7 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 d778e89462..371b64cdc3 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 fbc602329e..f7f54f0a6a 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 ae35c9d74c..ade6e1d894 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 14c8d24824..5c4916373e 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 985777317e..71f0ddc740 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 1de4b937f9..873c11e425 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 3cd0ead0f9..fe09aa861c 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 8db42d1f45..080d3accc8 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 e6817382b6..1ae0078880 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 6ddf536bc1..4b090e8a35 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 a6c66bbafb..38d0a67439 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 2a491725bb..4ab01e56c9 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 6d508713e7..361177c705 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 ca327164ee..c2ca5d14be 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 3d3ff248a2..f7df71bf19 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 d1872895dd..9f06bf2009 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 0b12946d8c..4da543a2d7 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 e3f2cef221..3a30a402d6 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 3a8a2df77d..4c46fd9253 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 df8f0f993a..c22ec65c29 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 f0ad86ff8c..571f8b34ed 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 7231c02e5f..920b61750b 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 7588947625..8c0f52dc3b 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 8f23935dd1..bda533900b 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 e9cf081b13..5e0ddf13da 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 355a4b9559..286168fb6a 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 32c2adb50c..fed8d4888d 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 00e9e24fba..9cd2cfc8c8 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 4ab53d25c3..14582f309b 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 fb60b2b404..20acb7c36f 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 f74db8e890..52b50c17fd 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 1be1b7fab2..84a4ecab83 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 21def5dbef..3e95aea867 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 550c00e41b..ce90bb7f38 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 2a3a810fb5..b7834e713f 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 289fb6fe5f..98d838b1be 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 a59799f0aa..5fb5ba27de 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 1ba0c04ef5..4d9699121b 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 e0bd824fad..9c8a693ff6 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 427ed83cae..bad6265698 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 628f0f777c..dbb6f3f917 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 4b3bc54720..95a291cce3 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 0dd31d9b2b..fb7f360642 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 c88997a3f9..d3726915a9 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 7e7f1240dd..b1e52bf1e6 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 96218713d3..2444df30b5 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 6df730ddfc..32505ef568 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 1c24537453..86c220b77f 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 fec7a1c4f0..df39b83d0b 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 7a5da6f203..153faf1534 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 19e823234b..407a5cfa4c 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 7c3cfb163c..e0c8cb0af9 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 b275c464eb..fcd6871c39 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 ed45d60e48..6cd991341b 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 e7bf3c553f..9bc036e1d8 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 e8a48922b4..72cb97f7cf 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 bc82888d3f..556959b68d 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 85018267b1..43bda4cf97 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 21f4377ce6..3e4c817bf8 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 5df36d74bd..c115c2cacb 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 ae601dabc2..eba2903f21 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 8bc982f292..50d289fc87 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 f21038c4e1..50a3e98cc8 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 a46bb9cdbf..372e8b3cb9 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 60861f7615..97bf685468 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 12702bce71..aafd560260 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 ef5b130293..c2354fa3b1 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 1e1dacc2fa..63d275cc4c 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 a14fa2c6ab..a7ab4ac8b4 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 220eefae95..1c1e30b897 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 5a360b8613..d5cd10e5d6 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 63ec60d0b4..1679997ed3 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 0d93daeac8..c4d5842474 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 988652d0d4..a1f7ebfa6d 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 f54fc37ba4..78cec7e379 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 d980be7864..fe4204dddf 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 234ccbf95b..2ce5cc63cf 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 4289056bcf..9d7d9aa5bf 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 f9f66c7531..d78cb09f33 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 5a6bbd4a04..9a5c95f806 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 a05f550105..329ad1de61 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 390ebc886a..8de0a7870e 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 b1f2c18656..28e559719a 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 b6e638c979..1d9f2aa628 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 0dfb5d71ec..7cc3d331ca 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 fd6866bc72..75f020fe77 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 4e89ed8ec5..61702c6194 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 bf673c0fe8..cb714dfd2c 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 7e387901e0..569d5d2ec1 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 4cfecb05f6..949151542c 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 a0e82946ff..fb36d04c44 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 01e735d949..07f4156550 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 952254e1b9..5b8bb62bcb 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 48cc18025b..29a36e42f1 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 00fbc34a1d..e631819818 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 b3089b62eb..ff7d609881 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 bcbb23ea42..072c7952e5 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 8c3f7d2da5..732e57c10a 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 3d2edcc419..7a016c7bee 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 8ae601b832..6987e9482b 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/1789] 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 4e00f1d8ec..2a9b991915 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 ad53206b25..fb7468ca33 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 19a5d6b96a..da8426c905 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/1789] [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 6dc6ec47e3..1650428bab 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 05aab74b88..3a18b983b8 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 7307fc8571..143187dd1d 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/1789] [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 1650428bab..52bc5808de 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 080d3accc8..5465a4fb45 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 be27dd03a0..0bc2758152 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 e268800f5a..e0a3c6efce 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 7ee5729483..1fbe97ae5d 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 9d6cacd951..17dc5ad4ea 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/1789] [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 52bc5808de..729d6066b5 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 3a18b983b8..5dc867fc1b 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 81b6bc27c6..80700ff58b 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 5465a4fb45..0000000000 --- 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 177b6b5843..bbd8d97a3d 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 4ab01e56c9..c997c65ec2 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 cb8c2d6543..7e47b6498c 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 9a15af910f..3dea120177 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 5f1a719b48..2be97ee777 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 0f5dc0d9e2..87d7a31a2e 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 96922b4c09..a0539b7168 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 0bc2758152..06e94ae718 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 9ca104d127..0000000000 --- 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 2dd864952a..1705f47461 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 a39fe6c67b..df06443436 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 e0a3c6efce..2e0cc7ee20 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 1fbe97ae5d..2f96b7016f 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 17dc5ad4ea..48060fdfbd 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 c5d0a651a6..73c2e19947 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 002a87f66e..e2a30437b3 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 a00324259a..aeba44bb19 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 31a9613e91..672b1f57c8 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 809ba5bd20..7b84648f28 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 a5be40c268..c1af0c7740 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/1789] Fix build --- phpstan-baseline.neon | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 80700ff58b..95c67cfed4 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/1789] [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 729d6066b5..d75f6b3a3c 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 db1c331ea6..98acab68df 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/1789] [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 d75f6b3a3c..ae40bcc88a 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 98acab68df..4e18f428fc 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 6063a96ef2..5b6700d80b 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/1789] [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 ae40bcc88a..b4a7fdfe32 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 4e18f428fc..dc9c6ec228 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/1789] Upgrading note --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index b4a7fdfe32..154776b331 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/1789] 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 95c67cfed4..f7e243fc88 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 dee1dc6c30..0000000000 --- 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 3e91fe8dbe..0000000000 --- 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 3a6c6dfb4f..0000000000 --- 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 d74a1ddd8e..0000000000 --- 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/1789] [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 154776b331..99092f8c03 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 70b2a54a72..e6bfb30cab 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/1789] [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 99092f8c03..a4f61a2a80 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 7923e9744c..e2b489ddab 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/1789] [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 a4f61a2a80..64ba4f5839 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 73c2e19947..029e982bab 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/1789] [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 64ba4f5839..fc7ea494cd 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 029e982bab..cbb555e412 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/1789] 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 f7e243fc88..3c0e92416c 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 cc939049bc..0000000000 --- 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 32fabbc757..b56a981bab 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 864f1fff21..620284912f 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 25f5bd3e84..f71d4dcf8a 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/1789] [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 fc7ea494cd..b87762ce06 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 d0e68e6453..2e1e7ae611 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/1789] 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 86914b4b26..a2f36452e2 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 366cbe5c06..275758e6a5 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/1789] 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 6a2c272882..dd968fd6b4 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/1789] 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 dd968fd6b4..6fc8eeabd0 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 3845352fd6..036d2b0f1f 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/1789] 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 2a9b991915..7bb7e2da26 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 fb7468ca33..cfb1a97642 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/1789] 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 0334aead77..0df0dc011c 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 4b17f15ed4..f92af453fb 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 0000000000..330594145f --- /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/1789] [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 b87762ce06..ad9f8379e1 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 4b090e8a35..1697b5f38a 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/1789] [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 ad9f8379e1..1012b56795 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 78cec7e379..7afcb0d9e2 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 5e3d2548c1..6a13877990 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 330c77562c..b7d563a615 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/1789] [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 1012b56795..9321607fa9 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 75857e5eed..7476af723d 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 372e8b3cb9..16090c44b0 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/1789] [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 9321607fa9..65733774f5 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 732e57c10a..0000000000 --- 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/1789] [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 65733774f5..8b14609497 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 86192c0a6c..12635a0913 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 0c355a2160..2f6d8a0cc8 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 165f41741c..d20760f847 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 62cd108a77..930473cf03 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 110fbea58c..1a3a37858b 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 195a807dbc..c32ef8c506 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 b911c21fbb..cc8e90f236 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 5703420e9f..82460ea1a1 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 9cb274782d..a7c56c56ed 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 2194a10cef..969a99df7a 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 853645c91a..0481c3939b 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 293e1f111f..fbe23d4534 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 fa64240e89..365431f4ac 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 dc9c6ec228..c868b64613 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 82b3645a27..20cfeafa67 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 eaca9d4572..ed622e5817 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 e55847aae0..751239ab73 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 7917cbfda3..a5b39335cd 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 0ffa96e002..890e13994a 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 fd6ecd5f03..d9b06d68bf 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 fb41aebbad..f7d92dd554 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 6ef8ff5ee3..fe0af6a812 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 7f48131262..912dd68499 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 0a0fdd88ef..bf9f5bde68 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 1523562cab..68be84499a 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 a8601a0b9e..c90a5b29e6 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 672b1f57c8..0d12a7f532 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 17e293973b..df1b7022ab 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 b8b5b38c6f..284be58a82 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 bc00486cbf..ba67e53322 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 e0d2924951..55190656a4 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 00a9141233..3f86776840 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 9fcd1e1895..b022f2b397 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 e2cdc07e57..d3a0083da6 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 8ca9c69c29..2fd278e34b 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 a456a0e846..ce306ee9f5 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 e6bfb30cab..1316f9369d 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 add4ffca45..2bea11cb04 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 5fe7d0c409..3ffc8f9db7 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/1789] [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 8b14609497..0d975f2429 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 686b70bab8..f619db2fef 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 0c1682209d..6284d454f1 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/1789] 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 f619db2fef..6af143689b 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/1789] [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 0d975f2429..5609946f04 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 5b6700d80b..33b1370100 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/1789] [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 5609946f04..9465e15518 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 33b1370100..95fc560c06 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/1789] [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 9465e15518..ee245c430b 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 95fc560c06..70c94efe76 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/1789] [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 ee245c430b..0199fc4452 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 70c94efe76..0dc13355f2 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/1789] 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 3020a8545c..ba0c7ac476 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 1548dbca10..caa24d89e8 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 a7df787390..0ae88bf65b 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 d9a4bd0ab3..2e6178c1a7 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 9287016809..b92bf25f4a 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 ddbf4c2114..7ef7b4e149 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 ddbf4c2114..66c19c7166 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/1789] 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 ba0c7ac476..2ad09cad2c 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 7ef7b4e149..7c3f71ae98 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/1789] [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 0199fc4452..0580805b51 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 6987e9482b..f3d7998da2 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/1789] [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 0580805b51..a3f6619a0f 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 f3d7998da2..2e12de1366 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/1789] [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 a3f6619a0f..0a302e00d1 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 2e12de1366..e76089a2ac 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/1789] [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 0a302e00d1..065b68b214 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 e76089a2ac..2094ae13b7 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/1789] [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 065b68b214..b065245f9b 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 6af143689b..02340153bb 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 2094ae13b7..81400df4c3 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/1789] [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 b065245f9b..100c8d213d 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 81400df4c3..0929b11499 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/1789] [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 100c8d213d..31f50bcea0 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 0929b11499..2c8941dec0 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/1789] 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 a91974e466..62c022c8e1 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/1789] [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 31f50bcea0..5088ba6f55 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 2c8941dec0..bfa15b092e 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/1789] [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 5088ba6f55..1806a07768 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 bfa15b092e..ff44d43b2f 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/1789] [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 1806a07768..831b93d9af 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 ff44d43b2f..4d838b2b3e 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/1789] [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 831b93d9af..7e31663790 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 4d838b2b3e..c00c7602ec 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/1789] Indent TypeUtils upgrading notes --- UPGRADING.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 7e31663790..59eec7ee5a 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/1789] Update baseline --- phpstan-baseline.neon | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3c0e92416c..5c198cf5ac 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/1789] 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 48cc18025b..a4d0e8d75e 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/1789] [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 59eec7ee5a..264980ab6a 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 02340153bb..d68b07e630 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 6284d454f1..96859d4c5e 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 45574a089a..b61904f26b 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/1789] 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 5c198cf5ac..9d922a2fc9 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 d68b07e630..659dc7f833 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/1789] [BCB] Remove `ConstantArrayType::findTypeAndMethodName()` --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index 264980ab6a..235bb0e936 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/1789] [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 235bb0e936..aa7e28968e 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 0dc13355f2..957eb84b01 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/1789] [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 aa7e28968e..e1b2c2406c 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 957eb84b01..f9f9be47c8 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/1789] [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 e1b2c2406c..1d56408d06 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 f9f9be47c8..bf0c70d01e 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/1789] [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 1d56408d06..568b326d88 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 bf0c70d01e..e54da2ad1e 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/1789] Update baseline --- phpstan-baseline.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9d922a2fc9..b64fc25c61 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/1789] Fix build --- phpstan-baseline.neon | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b7c18db8d9..3f40a5c43c 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/1789] [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 568b326d88..00d7c0766d 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 511ab3b9eb..4477db7fce 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 4bdc434294..f2e43632a6 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 6ef403a0de..facec02644 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 37fd21932a..34f66bb8bb 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 c868b64613..4a9a03dd6b 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 e54da2ad1e..5e24881e61 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 0000000000..ee77d47162 --- /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 ce306ee9f5..fec6ead728 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 7a016c7bee..c67cb05ff7 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 5538370ccb..f2e2109820 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/1789] 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 a2f36452e2..612ca1c5d5 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 275758e6a5..edd298b6c8 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/1789] 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 08a7ecfb87..65e47459f0 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 0000000000..039b2bd667 --- /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 155c0d6ea4..f0c8cd0025 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 a769c5f2dd..30f7dd3182 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/1789] [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 00d7c0766d..bfba8f8dd9 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 5e24881e61..498d59f41d 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 e2b489ddab..a433c4a89b 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 727fd65d01..62ed2ebb9b 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 ce605f3d5a..c2f0f96191 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/1789] 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 d588cea558..68ee421e38 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 d2d55336aa..0000000000 --- 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 9b9a9132e1..b7f438737a 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 8de0a7870e..7188551137 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 77de1aa1b7..22028286d3 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 5a1a007253..bac3f273ad 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 37de68b4f6..0a78f8d382 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/1789] [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 bfba8f8dd9..4fb0823cf3 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 4477db7fce..23d822be89 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 35ed6d3cf8..118c8d5b9f 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 498d59f41d..09eda329e3 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 f44e18333b..b84b381717 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 d5fab2b652..0000000000 --- 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/1789] [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 4fb0823cf3..cd3d44175a 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 09eda329e3..a257437061 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 62ed2ebb9b..9e814dfaf8 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 e9cb8ada70..3917423ff3 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 55dc30542b..d9d6eefe66 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 c2f0f96191..9770a62a72 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 cd0ee180cc..83ec097e23 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/1789] 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 9f4f4c42a5..2af3d80b5f 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/1789] [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 cd3d44175a..378e11d2ca 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 6baa6e0c6b..0b68559b42 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 659dc7f833..99cd3e3c5b 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 b4065332ce..6b2ff2afdb 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 9e6644c0f1..fd6c62afd5 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 259ede81fa..25ff7d770c 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 aef7210140..82eb478fc3 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 b81e30e846..3dc1cd56b0 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 ca67c3c2e2..36b143ed12 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 ba4faeded3..c6157c17db 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 7cb583f1dc..43f27903f9 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 e09ceb27ed..66ef61928f 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 eaf63ef8a1..fef9716d6c 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 82a26d08f7..7a7e137d9c 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 68ee421e38..1671c55aab 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 d78cb09f33..93db8e9834 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 be5d97660f..1bb9d201c8 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 ff5fd5f9a5..bd706e7c98 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 dc8f74ecea..85e6210699 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 96ba2f6be1..5710ce56a0 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 1f3ffee131..5bdbd199be 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 7188551137..ea1bc0c6c0 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 ab121486a1..7dbd382405 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 477cbdea74..1ce0c0dc71 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 1d9f2aa628..05a4888c27 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 65c3b8b152..d27576767f 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 3808304444..140ce0bd2e 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 12d263b0e6..2f348095ff 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 9f42773237..787d6136da 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 d8fb352e52..05cd89d0a7 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 20cfeafa67..de594f9683 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 751239ab73..708257ea1f 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/1789] 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 e7d2b401bb..a97d401104 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 2bf4e1b5f2..f836c7ee65 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 ff7332693f..0000000000 --- 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 23d822be89..0f3487007a 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/1789] [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 378e11d2ca..c3bf1e8995 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 99cd3e3c5b..4317d5945a 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 f2e43632a6..74f2c58c71 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 66ef61928f..09232a4d8a 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 7a7e137d9c..529bd5fbbf 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 85e6210699..331105aceb 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 5710ce56a0..227b23ddab 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 5bdbd199be..6aba725f1c 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 329ad1de61..8cd703c8b2 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 fb61a5c90e..6df44ad440 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 cb3932500f..f7c7c2da25 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 d7b8ee4599..bfafb5996e 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/1789] 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 d6e1361dab..c10150b748 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/1789] 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 a97d401104..e7d2b401bb 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 f836c7ee65..2bf4e1b5f2 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 0000000000..ff7332693f --- /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 0f3487007a..23d822be89 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/1789] 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 e7d2b401bb..1ebc818e9f 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 2bf4e1b5f2..184b4f0320 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 5dc867fc1b..3d05cf2669 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/1789] 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 5612e74ea1..f7d00465ad 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 8827656c48..2aec4d44fa 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 23ebd61ae0..e555874fc7 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 b420a0a9ad..cb57bddb4c 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 19f8f6a22b..65b3273315 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 ea5ce3970b..b703518ab9 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 c7e98d3643..d0ebd50613 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 ef72841c5e..0e6d6632db 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 817482ef93..af2b4255ce 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 c4a7769e28..9f4cf02d61 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 a39cb4ef8a..9288a58107 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 de0b4d2b49..af58d51738 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 f0f47ae021..8f6808f2b3 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 38e9227624..85c515fac4 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 eba2903f21..92a91a4da8 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 50d289fc87..f720897deb 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 50a3e98cc8..498dd64ce7 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 c2354fa3b1..b501dd67e1 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 63d275cc4c..10bb054179 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 c4d5842474..85c26f1b6c 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 c234e8e2d1..9ad470b0ee 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 d6d69179d5..5d25367e70 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 b7f438737a..5a3606a66f 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 f439f811e9..ee6ada6f6f 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 294cc94b62..550a7bee59 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 0d12a7f532..6db7a03eec 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 fec6ead728..e439af445b 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/1789] [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 c3bf1e8995..a22375dd11 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 708257ea1f..e09269ad83 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 775134aab6..2747320c75 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 f7d92dd554..27e7e623c1 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 bf9f5bde68..def0bbd74a 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 68be84499a..bb2e9448f2 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 bbf9dceec8..3caabeee83 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 aeba44bb19..43b68b8dac 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 6db7a03eec..e3711b8d73 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 55190656a4..5ef9ebc6c5 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 3f86776840..944d0f2756 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 d3a0083da6..03120df9f4 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 b8097947b4..cc13c23a99 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 3cba4b5bca..048ef5fb33 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 2fd278e34b..174a6a2509 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 e439af445b..2d152cd73a 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 1316f9369d..05d3f3ec92 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/1789] 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 1b1c695ef9..804619781c 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/1789] 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 853222642c..f2327d728f 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 7bdc4d1e5f..eda1ad4ee3 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/1789] [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 a22375dd11..1e33daa557 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 143187dd1d..1b399f95dd 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/1789] [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 3a30a402d6..2aec877cf5 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/1789] 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 2a3f437a81..f7230851e4 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/1789] 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 612ca1c5d5..35b021fe66 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 edd298b6c8..f1585bc0a9 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/1789] 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 4fb9f47156..0cd90db645 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/1789] 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 fb3d949c4e..7da56bdff1 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 85121ad337..40db6547a8 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/1789] 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 23d822be89..b1010cff7b 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 facec02644..ee7ef34786 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 d51a97b3e4..508320fb8b 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 0000000000..aee5112add --- /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/1789] 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 6d03b43f10..e8af9137b0 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 87b74c9af4..e74f69460f 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 9e56ea330f..f0f11de2f4 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 dbb6a49ffa..7421f3b896 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/1789] 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 1e33daa557..c7756d49ca 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 f7f54f0a6a..1426b804a1 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 bdfce9f04c..e812086830 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 3e303b82b9..64aa593067 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 9a5c95f806..804d049b43 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 569d5d2ec1..a587099844 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 930473cf03..d41d3c510f 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 1a3a37858b..5f8705cfc5 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 c32ef8c506..48bb4b3179 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 cc8e90f236..9815225062 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 82460ea1a1..6dca5f0514 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 a7c56c56ed..ae15b4623e 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 98503a13a8..a28497912d 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 969a99df7a..635f7d37ee 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 0481c3939b..ec3a9908d1 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 f508447135..fdbc9b0bcc 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 fbe23d4534..50ccdb308e 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 365431f4ac..113d0eb689 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 4a9a03dd6b..93d0094378 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 dc1245ad45..c27a3d6cd0 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 72be662c95..df059481e6 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 de594f9683..0f733ca60a 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 ed622e5817..4a74ec015a 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 e09269ad83..90bef92255 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 aa5d8af0a6..96a1776e3f 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 57c2fe5d8d..a7c8095e69 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 a257437061..0d8d254eff 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 cbebe5b48b..a01321c138 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 3aeb1e2e18..0ac763af76 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 9226acacc2..779dc83169 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 a5b39335cd..a6fda677b7 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 e0bc8c3340..5bb5d0b5b7 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 345465b306..751271aef3 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 890e13994a..21d0a96904 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 d9b06d68bf..f7ecec9d86 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 2747320c75..f13d52d4e3 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 deeb9b6ddd..8c98baaacc 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 e631819818..f1c598c7aa 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 1ec43f153a..949f3bfa52 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 8cc0e54e5e..f362ecadd4 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 13440a54a7..e1a170a57d 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 ff7d609881..24f8fe2927 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 e8af52e322..c45a7be5fa 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 9c9f841703..e957bee8ea 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 f91a646c9d..cd19237733 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 27e7e623c1..3c193d15f5 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 fe0af6a812..b1dcbec2fe 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 32eab8b019..10a4cb2ea5 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 def0bbd74a..08f7f0f205 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 bb2e9448f2..66193ded71 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 57b932a398..93d14c6936 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 3caabeee83..29ae4f752c 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 c90a5b29e6..753dccc5d8 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 43b68b8dac..6878401ef0 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 e3711b8d73..2026e447f0 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 1144acd2f8..5b388ba712 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 430d38332c..5e4ef1aec3 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 4327f30bde..f62023f2c6 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 5ef9ebc6c5..ab8fc9d1a0 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 944d0f2756..c5d76697c6 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 b022f2b397..55fb28c2c1 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 963f98c191..954a7fffdf 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 2d152cd73a..0f89e01a93 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 05d3f3ec92..454f511c75 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 9df5411c47..d02b7d11f7 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 2bea11cb04..16bf203351 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 c5211fd650..ca38d7ec38 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/1789] 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 5ab5e725b9..d242403fb6 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 683a341f2e..fc9b44f79a 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/1789] [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 c7756d49ca..4778a9efd9 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 ea1bc0c6c0..d71fce1a4c 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 26cb1be007..548d9bde47 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 4dd23f32bd..4e38e44bcb 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 f2e2109820..333d9de4a7 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 fe5b5c6984..c3b06e73d0 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/1789] 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 f2b13d25c2..e14d95f33e 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/1789] 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 86610dc028..1b1bf800f9 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 635f7d37ee..132ae28edd 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 35a3ecb8ea..7d2eb3b2a4 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 a70ff82ceb..a941dd86ee 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 52b44a6168..4cecca14c1 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 7c9bcc0722..b6fa0c0b4a 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 d9d6eefe66..6084574e73 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/1789] [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 4778a9efd9..85a1d46a93 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 3d05cf2669..72d5042dea 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 1b399f95dd..2969aeca73 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/1789] [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 85a1d46a93..087dc7ee33 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 7d0b1998a9..9946e97c05 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 6f25d37cd0..a100809355 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 df0dd443c2..d8d3c28878 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 ee86187c8c..7ac3a0276f 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 854d9ded9e..d35d7dade7 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 f4138d043f..59ded11998 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 dfec2e90f5..089c9b5495 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/1789] [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 087dc7ee33..1afa0a1940 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 72d5042dea..79b8fce3b4 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 2969aeca73..d6e4a34882 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/1789] Upgrading note about Docker images --- UPGRADING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 1afa0a1940..22a6c35d39 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/1789] Fix --- src/Command/CommandHelper.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index c3cfbbedfd..fdd3d74317 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/1789] [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 22a6c35d39..679ada1d75 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 79b8fce3b4..5ab30e7e53 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/1789] 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 f71bf5c34d..60fb2a048d 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 c3d88dbcb3..be0c051842 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/1789] 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 5ab30e7e53..d627237282 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/1789] 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 5636417046..e77344de28 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 d627237282..e5082b1ce4 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/1789] 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 78455184f0..131376d66d 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/1789] 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 8d69f8840b..6b268bb146 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 60fb2a048d..35ad14bc74 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/1789] 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 8d8cf59016..570c637092 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/1789] 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 d9fe72d6c9..3b76ab1766 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 d8b1b775cc..30d798cdaa 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 e77344de28..bda46632c2 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/1789] 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 3b76ab1766..9243a74288 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 30d798cdaa..fbad323697 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 0cbccea5d6..b89a6dbddf 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/1789] 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 e5082b1ce4..08a5c8c099 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 830d0f9a62..5c5e33c803 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 82871e6145..affb0fc679 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 5a32351986..1ebd091d9d 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 0000000000..834df2b1f0 --- /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/1789] [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 679ada1d75..cdaad24fcf 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 afdbf2c5e7..728b5a039f 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 787d6136da..aa8b9f356a 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 be2eb86b1a..994b3943f2 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 7d5967fb2b..80ab58e8bb 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 ab3565a612..f4d3965026 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 af9d403b34..68f9fffc6d 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 b73b5a3509..5bac445f13 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 c8475e23a8..eccc960d2a 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 d8814af601..85059abcac 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 3076d2d3d8..63cd185b7a 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 bb2c2e83c0..f043cf3c3e 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 1fe931aca5..94e1c914b2 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 e33db8f0da..1b421c60a4 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 d41d3c510f..637b8e75b0 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 5f8705cfc5..52c2eeba10 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 48bb4b3179..2e4c9406fc 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 9815225062..61b9b60107 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 6dca5f0514..e2a08f54e0 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 ae15b4623e..b319a340fd 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 a28497912d..f556022dca 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 132ae28edd..0903b3e877 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 ec3a9908d1..a762290d5b 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 fdbc9b0bcc..f65b2bbe48 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 50ccdb308e..2afc15de79 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 113d0eb689..0d49eaf6cb 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 93d0094378..97ebb9a196 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 c27a3d6cd0..b4ed647e25 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 0f733ca60a..325a195d27 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 29f416d3aa..e8253d1bfb 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 4a74ec015a..41a9d16c74 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 90bef92255..ccd6c6cccb 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 775a4eb50f..8c6d8fd00d 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 0d8d254eff..de72286489 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 5bb5d0b5b7..cad4e394d1 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 21d0a96904..a9bea4a070 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 f7ecec9d86..ea2f583d8f 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 f13d52d4e3..b73a7efc94 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 3363818673..e0c579074c 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 6ae56cc228..897fb71524 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 7661078ca1..367c94e709 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 8c98baaacc..0e8f59cf5e 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 949f3bfa52..3e18bccf2d 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 e1a170a57d..979867b458 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 24f8fe2927..4f4c704aa0 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 e957bee8ea..8c0031ca10 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 3c193d15f5..0117d14999 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 b1dcbec2fe..512f88ac7a 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 912dd68499..481e9fb3ac 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 08f7f0f205..43814c3643 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 66193ded71..50266db06b 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 3eaddf53cb..3424c664e5 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 753dccc5d8..b8ba6be336 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 6878401ef0..946fb597d1 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 2026e447f0..30f8be4640 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 5b388ba712..e9cd001bdc 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 ab8fc9d1a0..9c720b5e52 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 c5d76697c6..1a269cb65f 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 0130d7e094..c393a956d8 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 55fb28c2c1..d6e48cd851 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 7452cd3c83..618b3b5494 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 03120df9f4..f5d2b1dce2 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 0f89e01a93..21b15c221b 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 454f511c75..704bd421b2 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 16bf203351..a49c642aca 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 cd8ddce5bf..98332b7031 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 375210eea2..f7552cba51 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 87d3bae274..a59308f40c 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 2d827f7271..f4d19a2760 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 9e814dfaf8..f2ee31c44f 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 1b39e5ff55..c33c33e5a6 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 e700d67a3d..1bf9013c42 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 ef878bf6dd..04a9c270ca 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 3efd727f4c..ef6a0faf8d 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 273394c6b6..30539fae85 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 71a57dda6d..c5bd9ac0a6 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 a1c83f7885..79c3ef9560 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 b6fa0c0b4a..c5dbfc07ab 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 2094ebf589..013c0fd15c 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 6d8cfb28ee..f17c18ea97 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 8f22550446..813349b91a 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 3a17311989..6b184eef8a 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 83ec097e23..46b5803374 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/1789] 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 5091fec153..0000000000 --- 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 6f937312ae..4e6216b466 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/1789] [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 cdaad24fcf..dc6ac842e1 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 4317d5945a..5d11b826c6 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 42a85108c9..925e35a50e 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 96859d4c5e..963a4e2194 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 fd75fa5306..c64a74cea2 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 06e94ae718..7542e3ccdf 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 7afcb0d9e2..cafc201341 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 ed1803e786..e1c62c60ca 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 2e1e7ae611..7d966f83d5 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 9940b28505..4b31de502d 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 6a13877990..ebae755849 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 b7d563a615..e38a8740dc 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 6483e72e86..0000000000 --- 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 2e0cc7ee20..3b7387f85a 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 2f96b7016f..7d18639f8c 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 48060fdfbd..00f4301c7e 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 4e3bdcc92d..977bac234b 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 ca1a822f99..2b3fa162d1 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 ccd6c6cccb..46101a4bfe 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 a6fda677b7..6d2e32c4a0 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 0117d14999..c6c6d6b644 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 43814c3643..5353a10290 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 50266db06b..21f58c0c29 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 29ae4f752c..f52c5ea8de 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 30f8be4640..804147910e 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 9c720b5e52..7d73571c4f 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 1a269cb65f..2e426b2e66 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 f5d2b1dce2..34785fb3f3 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 cc13c23a99..ff50c721d3 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 048ef5fb33..d16b86c9b1 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 174a6a2509..5a98ade9c4 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 21b15c221b..398bc0d4f2 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 704bd421b2..4e38c38566 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 ca38d7ec38..6ded7325fb 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 24bde42de8..9ef9924063 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/1789] 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 5b508c2e07..80a48f9808 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/1789] 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 80a48f9808..ccaf001a14 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/1789] [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 dc6ac842e1..7859c5f267 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 5918003a71..a4758648c6 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 5d11b826c6..ae1c02dca8 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 74f2c58c71..ec807de6fa 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 5affae3a6f..1006f33633 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 12635a0913..9fde7ef34f 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 6b2ff2afdb..847a444eb5 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 cb86e0fec4..51bddcaabe 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 82eb478fc3..66bd629a3e 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 3dc1cd56b0..bc0a557157 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 36b143ed12..e3dff92c38 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 c6157c17db..c02b5ea370 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 fd6c62afd5..5b67d210cc 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 0b047b08b5..33c8e72c00 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 43f27903f9..e8a65b00b6 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 943338a493..db8df05ab8 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 f8ae03e477..002a8a930d 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 09232a4d8a..e6770e08a5 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 5aa65587de..e680908c32 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 64aa593067..7e1388bf5a 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 529bd5fbbf..852392d750 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 1671c55aab..b167f1223f 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 93db8e9834..57609ab0c7 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 1bb9d201c8..e28f4cd259 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 bd706e7c98..2758c04c27 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 331105aceb..12eb38927d 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 c7d8cc141c..91238c18b9 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 5a3606a66f..c42863737c 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 227b23ddab..dc44c0d17d 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 6aba725f1c..4669701c79 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 d71fce1a4c..afa4f56a46 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 61d1852c7d..8ebb272bfd 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 548d9bde47..40b28e9ff6 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 a6139853fb..5b5cb6b4e6 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 ab5b182105..4dda7b8685 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 1ce0c0dc71..b04803c13c 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 32a484c3c9..2a94fc4da5 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 05a4888c27..ea6b278145 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 5fd19b3105..eaca01ec4c 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 607a08a37f..25f676b5ae 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 d27576767f..c19986d71c 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 140ce0bd2e..167493c0b8 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 2f348095ff..c14e51656e 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 ccaf001a14..3bf9c0c61d 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 aa8b9f356a..365ab626a1 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 8f91b45ec9..6874582743 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 95170eecb4..d01dbf75a3 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 bac3f273ad..c3c04089ce 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 0a78f8d382..38c588f9db 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 eee8ad3281..f48a8abc7a 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 e70d2eb292..13d0c1fc93 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 ceeb071238..e891b65fb6 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 4c380a67c3..d8337c97a0 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 d392b20500..d579157160 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 bfafb5996e..870d185d59 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/1789] [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 7859c5f267..5f34052a8c 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 ec807de6fa..dfb1b6c1f4 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 9fde7ef34f..71fdb78b46 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 571f8b34ed..3f500b62c1 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 184821b5a3..a202f67bcd 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 be6a9fca4f..5767635e27 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 ba53111760..39379b6663 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/1789] 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 8107aba19b..fefb3175fd 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 b49c717f22..4ba93f6805 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/1789] 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 086a09c3c2..d8cc9faf06 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 0c1682209d..b1fe0cff97 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 fe0644cd30..cdad83a96f 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/1789] 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 e04381033e..8633178d9f 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 8994a4754b..604038d1fa 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 365ab626a1..1ad8773365 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 05cd89d0a7..15c0bfb9ca 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 d1ca216791..39b4ee650f 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 c4030961cf..2ea1fb2777 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 4f45dbf9fd..b0d522f439 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 33612ff02c..0f98eca2d9 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/1789] 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 9243a74288..84559ebb67 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 0000000000..5d052692c9 --- /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 da48578fe3..ce4c43f2f7 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 9946e97c05..499277dc8b 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 0000000000..8a1b7a3992 --- /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 0000000000..9dc3ca3eb9 --- /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 0000000000..900a4ee636 --- /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 0000000000..5c7f12b38d --- /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 0000000000..40e1203120 --- /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 0000000000..e74887cb13 --- /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 0000000000..0cc5a3f5d4 --- /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 0000000000..cf84dbb4c6 --- /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 0000000000..7d5fcb38d3 --- /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 0000000000..47cdcab769 --- /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 0000000000..4d1f2153ba --- /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 0000000000..57f727da24 --- /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 0000000000..1a8bc8b4b7 --- /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 0000000000..9581e25ad9 --- /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 0000000000..cc773c1e06 --- /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 0000000000..fd397067b9 --- /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/1789] Added missing BC break --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index 5f34052a8c..96579f42c7 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/1789] Added missing BC break --- UPGRADING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 96579f42c7..bf3fee1b8a 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/1789] Update composer/pcre --- composer.lock | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index f1585bc0a9..916baf9f89 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/1789] UPGRADING: fix missing syntax highlighting --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index bf3fee1b8a..474ecaf275 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/1789] UPGRADING: fix typo --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 474ecaf275..d66361a286 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/1789] UPGRADING: fix missing backticks --- UPGRADING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index d66361a286..cc0fd98bda 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/1789] Upgrading note --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index cc0fd98bda..83e9631321 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/1789] Upgrading note --- UPGRADING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 83e9631321..161a09499b 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/1789] Upgrading note --- UPGRADING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 161a09499b..c1ed243017 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/1789] Typo --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index c1ed243017..2ab208b5da 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/1789] 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 461ab91c4f..b145afa082 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 71f0ddc740..dad528dc18 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 ee7ef34786..6dd4f821b9 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 847a444eb5..865abdbe04 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 66bd629a3e..71ea905c52 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 7d966f83d5..a557218fd5 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 bc0a557157..3b3279596a 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 5b67d210cc..5e2d3a9c10 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 33c8e72c00..e45f402bb0 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 e8a65b00b6..b49a71bb1a 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 002a8a930d..77fb213b49 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 e6770e08a5..33b355b844 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 b6023cae16..7c69274ef0 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 fef9716d6c..037f4e8137 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 8d601e9471..529a5011dd 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 852392d750..730f8c61e9 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 b167f1223f..8f1e21d7c9 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 f4c2d4f1c0..b5fa5f1a2d 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 57609ab0c7..cc36c1d951 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 12eb38927d..76c8e7cf7a 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 dc44c0d17d..1d157476df 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 4669701c79..6209323f77 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 afa4f56a46..d0e008a7ed 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 4dda7b8685..dcf68ca1ef 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 b04803c13c..33b70bafe4 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 07886541f8..f9107d4b23 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 0652b11383..e60cede66d 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 f65b2bbe48..759230b2cd 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 97ebb9a196..11304c9edd 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 325a195d27..353b5622ea 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 46101a4bfe..f4e50f8089 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 a9bea4a070..829f1cb8ed 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 b73a7efc94..f7c18de70b 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 512f88ac7a..6d375c7e2c 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 481e9fb3ac..24f2974ae4 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 5353a10290..8ffc633771 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 21f58c0c29..5e2291a38b 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 b8ba6be336..f9296f32ff 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 804147910e..c08fc0af10 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 e9cd001bdc..69f10e8dba 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 7d73571c4f..cb3a135431 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 398bc0d4f2..2e0557ba0c 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 c00c7602ec..65268fba79 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 4e38c38566..3c8b9ef1ff 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 a49c642aca..5895449145 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/1789] 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 c9ae804546..f5c64cd93c 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/1789] 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 f5c64cd93c..92eae571e5 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/1789] 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 fbad323697..d4927a56c4 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 0000000000..8c86e3c713 --- /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 0000000000..23892389dd --- /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 0000000000..f9f017054f --- /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/1789] 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 fefb3175fd..65cafffb9f 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 4ba93f6805..e9590e9402 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/1789] 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 b1010cff7b..22970c5201 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 b6dd1e2c2f..ac02e1f9e1 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 ac972f04a9..ace5a21c5b 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/1789] Upgrading note --- UPGRADING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 2ab208b5da..af436a4ec1 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/1789] Upgrading note --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index af436a4ec1..d23ee8f530 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/1789] Move upgrading notes --- UPGRADING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index d23ee8f530..68769233aa 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/1789] 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 a0539b7168..39ebcf09b3 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 f47d5d89a1..3f6ce4bddf 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 ba72a0d584..f0b45a47b7 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/1789] 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 8f78c9023b..5b81f82f1a 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 3cb1b93328..d3ef021189 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 3862059b02..b45c120089 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/1789] 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 3f8e6a62ee..769f37bd1c 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 a80e510da3..2b8d8e855c 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 430b0e02f8..aea1961e32 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 2816b5ce52..e0998e5252 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 0000000000..813518812a --- /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 0000000000..b06391db45 --- /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/1789] 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 67351dc4da..aa096cc686 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 40551504fa..073d131922 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 481d99f165..893192e250 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 d8be08408f..6694ad3e42 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 3bfd8ae7a6..7c63ae9d67 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 932a09c299..84474ea49a 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 fa603eb85f..ad07ee066d 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/1789] 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 7b84bf2705..6a8f4d6897 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 073d131922..091da8aa2a 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 7c63ae9d67..72c49cc286 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 84474ea49a..c038a01891 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/1789] Update changelog --- UPGRADING.md | 2 -- changelog-2.0.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 68769233aa..57adbd1012 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 84559ebb67..2a7a46631a 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/1789] 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 2a7a46631a..f0abba38de 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/1789] 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 b6391d651a..8451a48ebd 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/1789] 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 80c57788d6..f11a8f3ff0 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/1789] 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 1b094a1cd7..8923430fd3 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/1789] 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 eaa39f1d5e..8acded5318 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/1789] 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 aab4991158..0000000000 --- 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 8acded5318..0000000000 --- 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 62de0070e5..2c808909bc 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 22970c5201..63e2058a4c 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/1789] 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 8acded5318..d78223e40c 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/1789] 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 65af41abc9..66da20eec9 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/1789] 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 6b268bb146..1b3a8bedf2 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 2a0883c500..660c3ad5ec 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 9ff7a2b3de..0000000000 --- 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 ff7332693f..0000000000 --- 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/1789] Regenerate baseline --- phpstan-baseline.neon | 654 +++++++++++++++++++++--------------------- 1 file changed, 327 insertions(+), 327 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 63e2058a4c..370a77c831 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/1789] 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 de72286489..46adbf9a73 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 f0c8cd0025..155c0d6ea4 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 98332b7031..e701997c6c 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 f2ee31c44f..b047b86a69 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/1789] 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 d6c55e62ec..75dd681fb1 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 7c63ae9d67..ec7035775d 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 e1b25eb6c0..d35c1b3b5b 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/1789] 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 370a77c831..246118a8d5 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 c6c6d6b644..f3784184ba 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 19b2783093..52791b8b95 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 f1f2f60f40..510df695f1 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 6faaa574c1..fe3512a45b 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 255c936d22..4be30d9f49 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 c83001b6a8..43c6020744 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 e5003028bc..8111e9a965 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 e577240cb8..83b0f40567 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 bd993f3bca..14d161de9a 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 f0350b5179..8c0105a424 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 150b170ca3..4c19971d5e 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 5af9a8aae4..8a69195287 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 c5dbfc07ab..7648a80ae9 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/1789] 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 6dd4f821b9..f617b07693 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/1789] 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 d17122bbec..84ca5046de 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 1696d39d48..825f7d6c1a 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/1789] 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 3f40a5c43c..197663fec6 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 d32cc8882c..fb50126b59 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 2194a10cef..bcd5a93122 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 853645c91a..651d81560a 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 293e1f111f..084cc28d3c 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 fa64240e89..7bb8e5213b 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 4543e36b77..c1d163d97c 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 27d874deab..259952b41b 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 22a4d811e4..bb3eaab0e7 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 f72e8f3d76..777a47b94d 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 1523562cab..f9e2288e28 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 156a8c1a46..75f6a14b63 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 e0d2924951..0be91db587 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 fb03a4d170..3a90652de9 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 a68b357722..afafc91708 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 d6f332d2f2..1d1b948242 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 398529994e..b0ed8a7788 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 08b8d2c186..0a3479d9a8 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 b28c660786..87fa61e36f 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 67acc5964d..cf7ce49272 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/1789] 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 f11a8f3ff0..4ed1156445 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/1789] 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 52ec29d01a..e5b561af65 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 f3373d4c10..b41185b5c9 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 246237f97f..ca57318a59 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 9e97e8bc4a..b4d2f4de0b 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 0000000000..a97152f385 --- /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 0000000000..68bc97f2ac --- /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/1789] 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 4ed1156445..33e19aee34 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/1789] 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 dc1245ad45..bb4a47761b 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 745c1c2527..1db0ce23a2 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/1789] 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 33e19aee34..5ea2533985 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/1789] 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 b8bea027d3..ba174feb29 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 9780b2c69a..ec944bb630 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 f92af453fb..0a6b883e4c 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/1789] 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 0000000000..32e017a254 --- /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 ec944bb630..0e61d50e9f 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/1789] 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 8a9414fbee..3db816aeca 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 f91a646c9d..354067f0a9 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 ed6f552d15..3ae615b2d9 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 ecad48736e..275bc3774e 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 8637ad7c4a..1f60b4089f 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 e637669483..83c10f3225 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 644b9d7b79..623fea93af 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 cfb1a97642..814513ac97 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 71db7a6c98..9236447acf 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 1895254579..fe49aa8a2d 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 c7d6fcb84c..7c13500af2 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 e726c77d75..28580c448c 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 bcd7ecf616..873ad66b6d 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 11c2ed6a2a..3c85802e73 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 494c135d95..2d70eb4bab 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 11869c0623..1ab4badbe5 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 870aafd6b8..a6c4397893 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 cad6d811c2..44bc78cd45 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 9811b63253..73dff935f2 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/1789] 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 3e0d07184d..95dfa6cf8a 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 b534d6c94f..3669321889 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 abafcfbf08..42cd820e71 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 1b234d2280..11c65e5258 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 55926064a2..b0219c9fac 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/1789] 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 7bb7e2da26..dc30f2afdd 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 74a41fa235..202e5b1700 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 814513ac97..ea27c48004 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 ec25be47cd..3555613fe0 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 0000000000..db7127a15c --- /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/1789] 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 63e44b3ea2..64bdf2d605 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 c415fca42f..7ed783a8e9 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/1789] 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 ea27c48004..4f28696fb9 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/1789] [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 57adbd1012..6cfb483e2a 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 31b15792ed..246118a8d5 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 8fa3cd05ff..ab0e41edc3 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/1789] =?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 0e61d50e9f..dba7fcfe4e 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 0a6b883e4c..701dc1f049 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/1789] 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 17ba5ade7b..cebc157759 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/1789] 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 10b814e08a..4fdafdcbd9 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/1789] 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 7542e3ccdf..72f918f62b 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 4333954ff3..405eea46b1 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 6209323f77..e28f19f879 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/1789] 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 3f6ce4bddf..889ab78f1b 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 f0b45a47b7..e05628d968 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/1789] 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 3db816aeca..8a9414fbee 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 354067f0a9..f91a646c9d 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 3ae615b2d9..ed6f552d15 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 275bc3774e..ecad48736e 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 1f60b4089f..8637ad7c4a 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 83c10f3225..e637669483 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 623fea93af..644b9d7b79 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 4f28696fb9..e1929a795a 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 9236447acf..71db7a6c98 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 fe49aa8a2d..1895254579 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 7c13500af2..c7d6fcb84c 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 28580c448c..e726c77d75 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 873ad66b6d..bcd7ecf616 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 3c85802e73..11c2ed6a2a 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 2d70eb4bab..494c135d95 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 1ab4badbe5..11869c0623 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 a6c4397893..870aafd6b8 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 44bc78cd45..cad6d811c2 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 73dff935f2..9811b63253 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/1789] 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 fb50126b59..491af5b813 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 110fbea58c..700063570e 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 195a807dbc..fe57034af1 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 b911c21fbb..6587e85d35 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 5703420e9f..2a56264475 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 9cb274782d..1ab8d31eff 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 98503a13a8..1ced070eb5 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 bcd5a93122..f1ccf3c2a2 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 651d81560a..600fa6ca63 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 f508447135..372bcf70f1 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 084cc28d3c..c822edcc7a 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 7bb8e5213b..4d39431f7b 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 c1d163d97c..01ca997eee 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 82b3645a27..d9bbea57e6 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 29f416d3aa..aa28e59fff 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 eaca9d4572..2f58e27d15 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 e55847aae0..bbe6d5a73e 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 ea9b2f4660..b3080ceb91 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 aa5d8af0a6..f69d3633f7 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 57c2fe5d8d..72d7f0f5a2 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 259952b41b..6838aa336b 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 9226acacc2..603281d734 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 382911e69d..6350d17cef 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 42d8f4afc2..cf91e6e2d9 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 0ffa96e002..bd5b2a9082 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 fd6ecd5f03..893c13d2c5 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 775134aab6..24e4aa8f9f 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 3363818673..132cb20d6b 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 6ae56cc228..071475e215 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 7661078ca1..7398fe836f 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 66fd32a2fd..b344be3690 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 786c61302c..c48a457b19 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 8a9414fbee..1b914804dc 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 bb3eaab0e7..140e45a8ed 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 0000000000..30e9fe6acc --- /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 6ef8ff5ee3..662d2e1b0c 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 7f48131262..df6bee28b8 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 777a47b94d..20117d68b3 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 f9e2288e28..45f9cb05f3 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 3eaddf53cb..861165cdf2 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 ab50c2040e..7b60d5d3cb 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 a00324259a..af1e4d67d4 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 31a9613e91..3e8ac1371f 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 1144acd2f8..52dd0e7a21 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 0be91db587..9cf4da4dc8 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 00a9141233..13b0fee7de 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 0130d7e094..24a88696cb 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 963f98c191..3eb3eb845a 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 527d10b68a..4c8c6a7294 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 3a90652de9..76855da1f4 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 b0ed8a7788..6c5064f4ea 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 0a3479d9a8..6b828625b6 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 add4ffca45..4353c6efdd 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/1789] 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 24e4aa8f9f..6945ccaf40 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 7398fe836f..7661078ca1 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 b344be3690..b6cca8b290 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 c48a457b19..b0062b4211 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/1789] [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 6cfb483e2a..c46f3e1742 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 b02995393b..779bfcc98d 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 367c94e709..4858d69242 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 8e8df311f9..053bbf8160 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 bbd0790350..a2269616c9 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/1789] 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 1b914804dc..248d994266 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 f91a646c9d..354067f0a9 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 ed6f552d15..3ae615b2d9 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 ecad48736e..275bc3774e 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 8637ad7c4a..1f60b4089f 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 e637669483..83c10f3225 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 644b9d7b79..623fea93af 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 e1929a795a..4f28696fb9 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 71db7a6c98..9236447acf 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 1895254579..fe49aa8a2d 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 c7d6fcb84c..7c13500af2 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 e726c77d75..28580c448c 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 bcd7ecf616..873ad66b6d 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 11c2ed6a2a..3c85802e73 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 494c135d95..2d70eb4bab 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 11869c0623..1ab4badbe5 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 870aafd6b8..a6c4397893 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 cad6d811c2..44bc78cd45 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 9811b63253..73dff935f2 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/1789] [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 c46f3e1742..5fe143b645 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 1006f33633..a78fc63cbc 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 cc36c1d951..455e72a704 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 b61904f26b..48eea97d6f 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 c3c04089ce..86b4614133 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 3e00d609d9..62e5205b8f 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 a206842873..4c99637c60 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 0893a0e3df..a959dfbebf 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 1374071903..d3d4aa406c 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 ea97eebab1..871a3473fa 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 05da3126c5..78fde66dab 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 04424d84f0..94364bb21f 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 7401ee5629..10c3722dab 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 ef4a70adf3..c30f30e1c0 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 2b8a2d1d06..71b6b42759 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 beaa20c06b..b7da64e0b8 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 ac9e45da1b..07f76e888a 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 93ce993005..8fb1a219d5 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 74d3b2cc49..c42961fce5 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 203e0d048e..4e99e94cc9 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 57d85316c7..b5b09e9e73 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 64defc1524..26063e2fa9 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 54637fd45b..f66e10c091 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 ab283f5e23..f154fb5368 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 89ae748982..0fd8cf4475 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 b77802d759..46315a4eae 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 ce9d490116..52f29d37a2 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 2643ab810a..f2991857b7 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 72ee1bf9d2..5e803a6af9 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 d1a77c2f76..531b177af9 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 9fe2da3a79..4e04a9df28 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 779bfcc98d..a2bdadd7ae 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 e421d8f9a3..a62f9d6f14 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 f363f249cd..13f52b276c 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 053bbf8160..5ea5a7da04 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 a2269616c9..a630895bed 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 1f3ed5450b..2e9391e9d1 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 10fbe76b42..2dec6cc456 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 30e9fe6acc..61e9b4e9c8 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 861b927d3c..c7ce1499ca 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 ac875ed66a..2100901a86 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 f484a52c2b..810b0f5934 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 5d5dbc81bc..878dfc3623 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 f8c9dfc835..dd14d3f9d2 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 1f24c8a890..d3c0b94e7b 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 b80a6d1457..1a1beed6c0 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 93d6346813..a9fc033a67 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 8cd1ebcf74..3abe064e3f 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 990ac2e4b0..4729fdadf0 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 5fca1a75d5..f1b9a0b694 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 3bdf4e8631..feb18e97d6 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 052e9ab64f..39d4949ca8 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 4aac9818fc..b4757aaac2 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 16dde87001..6753d7ed20 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 6a0f25ef33..62d2b84d3a 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 897aed75db..6e2caf675f 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 e4f082d0bf..95c0bce136 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/1789] 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 f522ea446e..7c2f57188d 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/1789] 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 b0062b4211..a426d29f21 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/1789] 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 f0abba38de..b4967463c6 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/1789] 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 e48eb81bfb..a630895bed 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/1789] UPGRADING.md: typo --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 5fe143b645..5aae2eeb29 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/1789] 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 5ea2533985..f8c4bf30e0 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/1789] 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 c9357fa047..51891fb45c 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 0000000000..5fbd64483a --- /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 0000000000..267e1647ad --- /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 073fb23770..1e02759b5e 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/1789] 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 246118a8d5..fb4b06b279 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 46315a4eae..fdc0bdecef 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/1789] 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 6838aa336b..74786c7e89 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 413e1d5f2a..28dbd009c4 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 f16d288869..28d766512f 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 0000000000..afc7c1b6eb --- /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/1789] 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 fb4b06b279..246118a8d5 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 fdc0bdecef..46315a4eae 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/1789] 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 27718dc9c7..1246699ff8 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 0000000000..9b1e68ce17 --- /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/1789] 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 621831d168..04454c63b2 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 f47d5d89a1..65b02ffaac 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 669ccbef89..4396980e06 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 36e48df100..0154996741 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 a29fa35e83..e92f0b51f3 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 312688067d..002daeeeee 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 94b2806850..d0a80299b2 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/1789] 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 04454c63b2..493ec0e9c3 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/1789] 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 1b3a8bedf2..54283827b4 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 660c3ad5ec..810f663d6b 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/1789] 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 c559efc208..320b4eba1b 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 f6f5131a53..7b834e2cbb 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/1789] 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 246118a8d5..fb4b06b279 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 e77e12857d..fdc0bdecef 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/1789] 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 6a8f4d6897..583cceac81 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 9a4c1dd6bb..0000000000 --- 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 713c1502ef..8fbd112742 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 0000000000..5276d0eb47 --- /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 0000000000..50882efc54 --- /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 e28f19f879..e8fbc2e824 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 d0e008a7ed..53f5d80054 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 692ea7b699..835486fdc2 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 0000000000..94fe9a406b --- /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 0000000000..d1a572e1e0 --- /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 0000000000..da6135b967 --- /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 0000000000..54e3ba27b0 --- /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/1789] Cleanup - visitor already registered --- conf/config.neon | 3 --- 1 file changed, 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 583cceac81..990ae2969c 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/1789] 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 50882efc54..cc3821d9f2 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/1789] 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 5ba76a768a..8337b859ec 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 7c81447cd8..65b059073e 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/1789] 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 8337b859ec..18075f7e36 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/1789] 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 18075f7e36..1b66f26e2a 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/1789] 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 65b059073e..499ebb48e8 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 31886c52bf..524365e77e 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 c997c65ec2..7ac72d4fc4 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 a32bc2eb6a..218eb4e252 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/1789] 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 f8c4bf30e0..2dcf0feb8a 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 e50ab41805..4721b4e78b 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 0000000000..14fad8b550 --- /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/1789] Update nikic/php-parser --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 810f663d6b..3e705fbf75 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/1789] 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 7b834e2cbb..97737499a5 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/1789] 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 54283827b4..01d6a7219f 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 3e705fbf75..a0e88b728c 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/1789] 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 01d6a7219f..22bc7b1060 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 a0e88b728c..8f8c4a2870 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 4ba615393d..0c5c33759a 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/1789] 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 d8cfceb646..567b61f798 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 af739bc02c..88589db6cc 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/1789] 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 d8cc9faf06..5d6636683f 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 f9bfcf314b..231db1ba7d 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 402f8c7dd1..02fed04bcc 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 a14fa2c6ab..4ae755597f 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 06098d7938..6e483222d7 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 d980be7864..0254204a63 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 5347681a90..e2d463ab9c 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 b37d373772..5730ea3a9b 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 a17ef3d200..3ce9535164 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 e05c4c676b..c3e9fda73f 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 8a9aee1355..e0cbaa844c 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 6a1d2f16b1..40fb657bcc 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 a7519de7a5..c988c70c08 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 d49e7f9aa9..648636973e 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 6c21c3a33d..f574d76460 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 2fe0ab6bfb..d4b56da5f2 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 ef9ce469b5..c2eaef580e 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 46901218ef..3f437a0b91 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 30be451eae..53adafb43a 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 b0b6441c92..afa672f983 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 fa9a6ecb06..65653f833f 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 5eb8d8bdf4..dda84c8629 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 b08f12f32d..27ce74e298 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 32d64a68ad..8d5edd7890 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 e48ed2d785..2b3c563f2d 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 4b602b5fa1..e4e023ce21 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 75dd681fb1..3467703921 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 073d131922..0dec8f4d24 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 3e849cd1dc..c32491fe42 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 4a227ffd07..53d4c4e6a6 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 c429a30fdd..84c8a20325 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 bb0192d02b..e6658632cd 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 3107542f29..cc630fd0dd 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 66ce5db4ec..27fc50f21b 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 b291dab577..53ea994935 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 e411af4edc..a4bc35b848 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 bcb3b0cf94..f4d3b8dbbb 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 32332bb3ef..b3df6ccd2e 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 3810841ec9..0c58b3b41e 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 64c631980d..b4057fa327 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 87f1ca18a7..7576541dbc 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 7312ea2ef4..d8522eb503 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 132cb20d6b..c06b082d52 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 5b1f187c6d..270af37931 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 a67aa723dd..220414ca14 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 3d3cb9e8ca..7d6aebc6f9 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 071475e215..fa204aade2 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 084612c641..1ae72a3384 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 7661078ca1..8374c6c0ac 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 c29a175d2c..8f56cc6cbb 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 a38d656556..58460efaee 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 00fbc34a1d..30cd0e52c0 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 b6cca8b290..9a0fa73a59 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 997fb21238..cc196a07f4 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 1db0ce23a2..d27fd6fbdb 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 8ae601b832..9998d64422 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 e0998e5252..1e3afb4e70 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 0000000000..979fbc3636 --- /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 788be4cbd5..0538311ee5 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 35df206865..f9d15da674 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 0623a7d1c2..b997498703 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 a845993344..9276fec7c1 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 99ad839123..84351d9ea9 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 e66591168e..c04a5665a4 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 7a016c36ce..abbc514279 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 a76c2eeab0..06400bd536 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 e5d7848498..1cda1bcbcd 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 8b9de2cdaf..8a1ff456f9 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 8ae819c99b..7f0da436e7 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 0fc67c1b24..edf5d62201 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 7c9e179295..39126a88f2 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 f01c5e9dfb..f34fb5ffb9 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 93b5b9303e..7a46d849d3 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 fecb8a1045..89e29bb606 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 0762900901..49fc0b97d1 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 0000000000..a09d113367 --- /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 d5a333491b..27fa039ef4 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 5b708cad89..480373825a 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 9d4dc0bb3e..c2916f9864 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 f52cae0d04..74ad37a779 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 fd9688445d..cd996b4a21 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 bd92fe752e..324946835a 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 952016e860..374397cd0a 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/1789] 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 524365e77e..f5a9d9e713 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/1789] 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 f5a9d9e713..9536925b9d 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/1789] 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 2dcf0feb8a..c9eba8373a 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 11c65e5258..d242a5410a 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/1789] 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 f8149dad21..93cce45dd9 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 58d53a808e..c9435f826e 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 3e1448e6ff..a508035d19 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 6fd537211d..b24570b298 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/1789] 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 aa096cc686..3146a8fd3f 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 0000000000..be867366b0 --- /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 3ecbecaa7f..23338924eb 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 7fa89bc896..3d4cda24f7 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 0000000000..ed56b46bd7 --- /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 0000000000..e8d009fe0f --- /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/1789] 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 37b26eb3f2..1363deb2fe 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/1789] 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 979fbc3636..c73ce4ab38 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/1789] 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 d0a65124cc..de61bd96eb 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 0000000000..5c9f80cc08 --- /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/1789] Update simple-downgrader --- composer.lock | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 8f8c4a2870..beef5f73d8 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/1789] 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 51891fb45c..c4f626994a 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/1789] 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 4d56668a56..6d486bc70e 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/1789] 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 c4f626994a..8914cca904 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 0000000000..de4a392c33 --- /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 0000000000..e33fb45ab0 --- /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 0000000000..3f491198cf --- /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 0000000000..0a18225531 --- /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 0000000000..a75127a356 --- /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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 35b021fe66..6597076790 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 916baf9f89..b3c3720962 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 6597076790..866afda425 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 b3c3720962..aae4ed9cd0 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/1789] 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 6d6af362f1..530886afff 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 0000000000..5d052692c9 --- /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/1789] 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 530886afff..6d6af362f1 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 5d052692c9..0000000000 --- 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/1789] 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 e8d009fe0f..2b5c9c1d40 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/1789] 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 fb4b06b279..a652b6a836 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 8fb1a219d5..488dac119e 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 b382891275..68f2992096 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 52791b8b95..d64cd88617 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 6113db22b3..d24d24338d 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 04abebab3c..99cbb8db71 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 05684938b8..d172b358e1 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 8e6fa67cf2..c0b79ffbae 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 07c06367dc..4710482534 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 b8f0e6793d..2b75c17aba 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 4bfd3140e0..9ffde9da83 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 04494a96df..37a986b0ce 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 5d341b6290..6f05e9b9f0 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 c3430cc1b5..30b9527e10 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 eb227de3f6..2d8ef21d7e 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 87fa61e36f..e2faabd113 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 7ea0a40ff5..6808bf36b3 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 a9fc01c947..18074963a4 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 8451a48ebd..a1cbcc9b3e 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 f6609f3f8f..6c278c21ed 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 fff8d13dff..4b4ce770d1 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 7d70679585..17267e0001 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 cc02033da3..c01b4bdbdc 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 c6b49e87e4..b31842e05f 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 057067411c..c60d776d38 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 1dee1a63d5..a2e63375a7 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 4da94355c7..d88555a863 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 ce75fa7197..353b1d91f9 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 89bfa50a22..596e97634a 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 38db914722..1151d370c1 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 873ad66b6d..60840465c1 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 7b7b7b8a01..da2ae35787 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 8b470ba316..d1e8fd92a0 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 735e85b545..533e483682 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 59fa2afa13..c544802655 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 97cc9d0842..fee2d7bed4 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 82e09e0bd3..fc70c128e9 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 72f5a47e7e..abe041686a 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 b9e0920141..a115a32359 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 f5511bfc33..41d62d1c36 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 b45c120089..0383dadc52 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 f38c46cddf..67845acd46 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 eda1ad4ee3..98927b4bd2 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 ed96439687..88b0eb949f 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 c3842d461b..fe23c44a83 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 4c19971d5e..5ebfb238e7 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 f3ea56d12f..c092304ff1 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 155c0d6ea4..8a1ce3ba51 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 3616824c7f..e75b7cec10 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 2c2807e48f..97b5841941 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/1789] 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 0f8d05a8d8..f18941039b 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/1789] 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 f18941039b..0f8d05a8d8 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/1789] 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 866afda425..5b816f8746 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 aae4ed9cd0..91ca3e1931 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/1789] 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 6e483222d7..43f93cb8f5 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 0eaa4471d2..f059d14c4d 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/1789] 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 d187d34c96..a1fee9d3a7 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/1789] 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 a652b6a836..122462ff00 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 2dec6cc456..496910eab3 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 d24d24338d..e97a5f4a06 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 99cbb8db71..36bf122d87 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 d172b358e1..36d7837034 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 c0b79ffbae..cedb50ddb7 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 62350f5992..07dc05f399 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 2f02f1e733..ef91f40c36 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 bf620b508b..288ba539a2 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 aa21248ec6..1b788158d6 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 6f05e9b9f0..86a3bb72cf 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 f059d14c4d..10049a8317 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 e2faabd113..847f535df1 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 17aa63957a..913f1e9034 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 cf7ce49272..52d511c163 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 4f66f4f0af..19d5aeb15f 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 a7d33d1538..65634c415b 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 1d75efdcbe..3cfbd308f1 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 0a8d1b4b48..5e5eb30ca2 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 510df695f1..14732b77b6 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 297484c2cf..6fd566cd62 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 af941f8098..55a1c372a5 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 d6bae28afc..b6f2302722 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 0f6d43963e..56cd257c5e 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 859b504e50..ec9464ebd3 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 353b1d91f9..1628ad6859 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 edbfebffc5..09a7ad92ea 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 fbbcff38a8..354577f098 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 6d86c88014..e76b112f4d 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 69fc99f613..172fa40b4f 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 d1e8fd92a0..eacfb06af6 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 caf0a17c87..c51ea31efc 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 a80e8b066d..647d44f718 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 a7cdc6540a..af34c0da25 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 c2bf853776..6b699e598a 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 e92b006713..93dfe0d147 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 5c60d3dec1..2afa164340 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 ca84648938..424c7ca236 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 96dd5ee26c..209a9decf5 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 5ebfb238e7..41200b9ad5 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 bfe756a020..2d57843622 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 24fb2c4a7f..fcf698a9ec 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 97b5841941..27ebe729ae 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 7648a80ae9..c5111cbeae 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 6084574e73..2694d0f3eb 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 eebdb08014..ffb42f1edf 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/1789] 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 07390c7b8f..74e943d911 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 0000000000..d98a2cc721 --- /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/1789] 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 e97a5f4a06..8338e6f13c 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 2455d6ace9..017490d534 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 7bbc485b5a..c0a4e8dd7a 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 0b7058de01..f62e1a6628 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 79b7af7b18..14dd7b5d0d 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 2cd24b7c9d..5816daf659 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 103cb0a003..9f5ca43682 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 10de823980..db378c95aa 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/1789] 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 88d4749a45..76aac5c080 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 7842bce806..3b033abd6d 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 07568e3768..e7e6efcd13 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 d9b7a85b78..e2810b0e42 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 1c35f8ef7c..1e30f3b562 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/1789] 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 d71fbfbdfd..4823b1c90e 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 74e943d911..52eac72312 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/1789] 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 9e0afc2f64..92912a0f9e 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 1e30f3b562..330e326e8e 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 74440d64d8..5c3c24924d 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/1789] 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 52b66c154f..47b0d2477c 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 0000000000..0ff71d5025 --- /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/1789] 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 92912a0f9e..ba67e6a9d6 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 3f32efd774..275583f027 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 17e368b276..0a46f4b75e 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/1789] 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 4823b1c90e..1e64b54187 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/1789] 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 d0a53d8362..5978bf90e0 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/1789] 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 01ea01b977..8035809eb6 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 577b87c127..c468ffbc0a 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 84c94b44c4..532f660fad 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/1789] 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 04cda78057..28d8d8b36b 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/1789] 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 c6c4934b44..f94cc44244 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/1789] 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 5b816f8746..2614dcf584 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 91ca3e1931..c0f104e2b7 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 0000000000..e01d1ff81c --- /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/1789] Upgrading notes about 1st party extensions --- UPGRADING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 5aae2eeb29..bc65ba774e 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/1789] 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 4fdafdcbd9..41282ea069 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/1789] 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 80c2c2cd1c..aac23594f0 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 bef8714c74..c3ff064825 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/1789] Remove dead code --- bin/phpstan | 9 --------- 1 file changed, 9 deletions(-) diff --git a/bin/phpstan b/bin/phpstan index 978d755ac1..ba27551db6 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/1789] 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 aac23594f0..b57ec8dd52 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 c3ff064825..271b9c0192 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/1789] 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 5d6636683f..401f011dc5 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 8b015c91ab..178c16b4a8 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 0000000000..76d79326e8 --- /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/1789] 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 3146a8fd3f..5ad99397a9 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 49a3395d2e..cbec53d0d1 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 fec1521768..1e2e938678 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 401f011dc5..90b3fdd465 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 0000000000..e60612f820 --- /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 0db119501f..7d9c764132 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 14efcc7480..8f9fdfd44f 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 20117d68b3..0b9d87394a 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 4721b4e78b..96712b66b9 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 3e05dd8b83..b6389bd5ee 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 7a46d849d3..b9c196420d 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/1789] 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 197663fec6..73144c4ac1 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 e60612f820..ba7c6c618a 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 74786c7e89..1b770d6889 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 96712b66b9..01157d82e1 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 b6389bd5ee..70882c4ca4 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/1789] 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 76d79326e8..18942d7080 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/1789] Fix --- src/Type/TypeResult.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/TypeResult.php b/src/Type/TypeResult.php index 18942d7080..5d10bced1f 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/1789] Work around simple-downgrader bug --- phpcs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpcs.xml b/phpcs.xml index 8a8a89df38..7ec0a21da2 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/1789] 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 b57ec8dd52..fd100caeb6 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 271b9c0192..88147fa23b 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/1789] 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 0000000000..6b3bd6db5e --- /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 2c808909bc..c250ea9eec 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/1789] 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 73144c4ac1..050d8a1d2c 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 92eae571e5..969d0be6b4 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/1789] 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 45f7b490b1..9145005d20 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/1789] 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 969d0be6b4..afe1373704 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/1789] 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 afe1373704..f4f60df2ae 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/1789] 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 b237cdd33a..e5a45b8a10 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 0000000000..865023c725 --- /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/1789] 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 0f15683606..ce937ac0d0 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/1789] 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 d27fd6fbdb..f1860d1abb 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/1789] 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 ce937ac0d0..71c348f7ec 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/1789] 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 f4f60df2ae..afe1373704 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/1789] 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 050d8a1d2c..1bdc95b954 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 f1860d1abb..ccbb29f97b 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/1789] 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 afe1373704..f4f60df2ae 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/1789] 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 81d166902f..416583546f 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/1789] 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 1bdc95b954..1521bbb1a0 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 a306833e8d..e5caa1117a 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 140e45a8ed..6a8d2ab329 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/1789] 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 e5a45b8a10..f80796bce5 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 0000000000..6a3dd0bbb0 --- /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/1789] 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 f80796bce5..ebdf3a03e4 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/1789] 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 f4f60df2ae..5831cdaaaf 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/1789] 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 41282ea069..8dadf24aba 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/1789] 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 fd100caeb6..a14b805eaa 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 88147fa23b..caf98eda55 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/1789] 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 8d6cd25cd5..03a1a8aad7 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/1789] 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 5ad99397a9..7c88f87a31 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 90b3fdd465..2724ba1fca 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 5831cdaaaf..fd6ac13fc3 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 0000000000..c77059e214 --- /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 0000000000..1a01e5f97a --- /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/1789] 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 a14b805eaa..28752bbba8 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 caf98eda55..2c31e7e3db 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/1789] 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 dc3167a38d..36b2c569d8 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/1789] 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 530886afff..eca615b3ce 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 8b84920a73..b4a0ac5fd5 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 8dadf24aba..81d852e90b 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/1789] 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 35e364f15c..c21a2099c3 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/1789] 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 28752bbba8..1afe40674c 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 2c31e7e3db..4a1e51a351 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/1789] 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 2724ba1fca..90b3fdd465 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 fd6ac13fc3..fc7b1e9df7 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 1a01e5f97a..f6894d4724 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/1789] 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 e307d1380b..24156dc09b 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 0000000000..cddb7e7989 --- /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 0000000000..713d73a5e1 --- /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 0000000000..88a165e931 --- /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/1789] 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 7c88f87a31..f9d8ca018e 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 1521bbb1a0..498207dbe7 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 4672fd1a96..62dc52abf0 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 0000000000..5291652129 --- /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/1789] 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 90b3fdd465..c24defb1f8 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 6b828625b6..b7e794378a 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/1789] 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 c9eba8373a..b678f48e5b 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/1789] 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 f9d8ca018e..11e845136d 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 0000000000..8b25b36491 --- /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 619ee2aa81..07a7c28080 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 0000000000..220d8fa0ef --- /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 0000000000..97c514f427 --- /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 0000000000..5caf828f53 --- /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 0000000000..f3b5b0b822 --- /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 4b00fb720e..cec109d5f8 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 0000000000..a749bd8294 --- /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 0000000000..393468d42c --- /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/1789] 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 fc7b1e9df7..4a8264b674 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 011d15d6c7..1317b3695c 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/1789] 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 8b25b36491..0e798eb5c0 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 cec109d5f8..123fa4259f 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 0000000000..e4a3348eb3 --- /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 0000000000..1c267ffc62 --- /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 a749bd8294..3b7d7f9e13 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 393468d42c..ab2b7df3fb 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/1789] 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 275eea976f..cb8b4c5870 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/1789] 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 bb4a47761b..d6b43fbd85 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 cc630fd0dd..aea8573131 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 cc196a07f4..dc58af565a 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 b7e794378a..f0bef45903 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 65d7f22f1d..4a5f5bb781 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 046f9c8403..571f97d988 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 6d374a6f1c..fcbc0643c9 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 0000000000..ac2ed03a93 --- /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/1789] 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 b4967463c6..1e2d866a1f 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/1789] 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 9c134d199c..7577e5c04f 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 7d1bf16616..df5c15ddec 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 d6e4a34882..6c7f9c40e6 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 0000000000..3a9875b460 --- /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 0000000000..4d4ca141ef --- /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 0000000000..038f559122 --- /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 0000000000..3a9875b460 --- /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 0000000000..869fd2ce42 --- /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 0000000000..28d770f3bb --- /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 0000000000..3a9875b460 --- /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 0000000000..b6303c6b77 --- /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 0000000000..d4d06a34eb --- /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 0000000000..3a9875b460 --- /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 0000000000..b73464d219 --- /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 0000000000..2f652079a6 --- /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 0000000000..3a9875b460 --- /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 0000000000..9f9b263871 --- /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 0000000000..e8876bd78f --- /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 0000000000..3a9875b460 --- /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 0000000000..9be64619f1 --- /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 0000000000..d4d06a34eb --- /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 0000000000..3a9875b460 --- /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 0000000000..2c63c08510 --- /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 0000000000..28c8a3183b --- /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 0000000000..96977def5f --- /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 0000000000..3a9875b460 --- /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 0000000000..d6103988c8 --- /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 0000000000..3f201eadab --- /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 0000000000..3a9875b460 --- /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 0000000000..2da0adaf1c --- /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 0000000000..003e5e1484 --- /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 0000000000..a9afaa4b65 --- /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 b209533fac..8176fe8abc 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 67a98408a0..f111da14ec 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 7ac72d4fc4..75c79d6678 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 0000000000..f9b41690a3 --- /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 fcd6871c39..83ca1245b5 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 d926420e77..bd1bfcabf5 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 c578ef06fc..0190ca0e82 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 87d5bab299..aee824bfbc 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 9a5592bf40..d79ebf0706 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/1789] 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 8914cca904..4b90be1ec3 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 0000000000..61ead86667 --- /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 0000000000..a072011fe8 --- /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 0000000000..b383d88ac5 --- /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 0000000000..306701cd6f --- /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 0000000000..c24d0f3653 --- /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 0000000000..5c237f25e8 --- /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 c24defb1f8..df85c5fa35 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 4a8264b674..dda335f566 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 9cf4da4dc8..5fd6f7e358 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 fcbc0643c9..eaa0465a42 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 0000000000..89209ffe37 --- /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/1789] 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 df85c5fa35..5659a107bc 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 b727ba76e2..dd583aa13b 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 0000000000..690def3bc4 --- /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/1789] 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 0254204a63..c79f2a6bed 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 e5496fc646..70d5379701 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 0000000000..a8a4bb8555 --- /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/1789] 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 dda335f566..1a6a069d28 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 fb3a9dd5b1..b86de8617e 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 01157d82e1..48de5a95b4 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 70882c4ca4..f98e7e11aa 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 ad9fc94be5..f99d9352d9 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 0000000000..d7ffdde9dd --- /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 0000000000..c27592782f --- /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/1789] 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 1e2d866a1f..1756a96aa0 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/1789] 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 1afe40674c..23c5c46c7a 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 4a1e51a351..3197c29134 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/1789] 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 fb8b5985e7..68864c18b6 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 0000000000..0c190b23fc --- /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/1789] 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 8b6e3c4541..cee4acb569 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/1789] 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 8633178d9f..e12f906c00 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 604038d1fa..7dd65488a0 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 eb714ab171..10e4e50212 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 15c0bfb9ca..162894e266 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 39b4ee650f..63e1b93c7a 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 2ea1fb2777..5eb21c2128 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 b0d522f439..19bc52fe80 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 0f98eca2d9..e6b2c9dbb5 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 cc614cef95..a883c768d9 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 f5c779f171..25d9ef362b 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/1789] 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 217d8576cd..b0b11a10ae 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 ebb22df8ae..b73fa09641 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 b914db3141..9c86812e92 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 9e9588d219..cad3ca757f 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 0000000000..0db93a1cfb --- /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 0000000000..99ab186f62 --- /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/1789] 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 f3aad9f7da..a98c638d24 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/1789] 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 4cfecb05f6..2548f3dd44 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 30e9fe6acc..dd28cec78f 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/1789] 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 21071b8ed6..6d7233d38a 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/1789] 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 5659a107bc..3993085894 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 1a6a069d28..31dde8718b 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 0000000000..9b07f9ab2b --- /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 0000000000..e7c0bef9ff --- /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 0000000000..47811f500e --- /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 0000000000..f15cdeb811 --- /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 94f0b0ffeb..9a2346eb77 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 0000000000..ab260ad44c --- /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/1789] 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 b5863a4b80..fb481b9923 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 103eb129b4..5fa112a57f 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/1789] 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 3993085894..5659a107bc 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 31dde8718b..1a6a069d28 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 9b07f9ab2b..0000000000 --- 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 e7c0bef9ff..0000000000 --- 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 47811f500e..0000000000 --- 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 f15cdeb811..0000000000 --- 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 9a2346eb77..94f0b0ffeb 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 ab260ad44c..0000000000 --- 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/1789] 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 b0b11a10ae..3b52a47aad 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 b73fa09641..92084ea3a0 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 0db93a1cfb..aaafea889d 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 99ab186f62..d45bb08576 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/1789] Update UPGRADING.md --- UPGRADING.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index bc65ba774e..1b76768ec4 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/1789] 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 1756a96aa0..b504a3281d 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/1789] Update PHPStan extensions --- composer.lock | 53 ++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/composer.lock b/composer.lock index 3197c29134..4a377463ad 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/1789] 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 b504a3281d..0000000000 --- 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/1789] 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 be0c051842..b4abdf4947 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/1789] 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 28d8d8b36b..dcc27d6be7 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 1b76768ec4..0000000000 --- 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/1789] 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 0000000000..1b76768ec4 --- /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/1789] 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 cddb7e7989..e798af6bbd 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 713d73a5e1..76e0bea581 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 88a165e931..6e0af08f03 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/1789] 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 c79f2a6bed..ff45172fd8 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 58460efaee..29ecd48720 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 8c3f7d2da5..eee42d0f1b 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 3e8ac1371f..7427aa167c 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 0000000000..c56b47dfcb --- /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/1789] 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 8f1e21d7c9..731d4973fe 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 53f5d80054..d32c16e75e 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 5a8b677229..6b2a5a0c37 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 0000000000..1251bf5933 --- /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/1789] 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 1a6a069d28..5c4d86526e 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 b86de8617e..04ca04cb26 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 191ba39134..da076db1c7 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 0000000000..e6c35c967d --- /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/1789] 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 6b2a5a0c37..abbe2927ab 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/1789] 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 23c5c46c7a..3400105a87 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 4a377463ad..d4f8f73719 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/1789] 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 3400105a87..e739daa7f5 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 d4f8f73719..2d188ccc6a 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/1789] 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 dcc27d6be7..adea89e232 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/1789] 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 319379ab2a..afe9786db0 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 6fa6252277..f0deeee108 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 0000000000..feb1795d58 --- /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/1789] 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 40fb657bcc..623b593f5f 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 1246699ff8..fbb5e41439 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 0000000000..c2a5618cd8 --- /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/1789] 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 e8e9e3a457..50094cc573 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 ce73048a46..2e05c6160d 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/1789] 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 50094cc573..ce29b37d2a 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 2e05c6160d..d986969c3e 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/1789] 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 feb1795d58..94671eec7a 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/1789] 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 e739daa7f5..2dd8549659 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 2d188ccc6a..57ec0c62c4 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/1789] 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 df5c15ddec..77b59a5d3b 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 8176fe8abc..846188b985 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 f111da14ec..5ccc516e42 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 92f0712e46..4560fde44c 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 ebcf7c130e..88b81022ee 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 aee824bfbc..31cdfadb78 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 a1f3bea048..7b48df20fd 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 70dceda653..9d9d1b1fc5 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/1789] 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 6d7233d38a..96505cbf9c 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 77b59a5d3b..739e8247dc 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 0000000000..3a9875b460 --- /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 0000000000..8fe9570814 --- /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 0000000000..003e5e1484 --- /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 0000000000..a9afaa4b65 --- /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 d79ebf0706..7ca5d8bac9 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/1789] 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 739e8247dc..3f4414b606 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 7c3057190c..35cb6a862e 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 83ca1245b5..8520f6488d 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/1789] 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 b678f48e5b..46492450d9 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 11dd4fa773..9d26a518b6 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 b37fe5a7a5..bb05fe2b6a 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 c57f2ef46d..dff42bc482 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 123fa4259f..c105b2fe64 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 0000000000..1a6cb12c7d --- /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/1789] 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 2614dcf584..9aac57806f 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 c0f104e2b7..7a215f2f76 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/1789] 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 ccbb29f97b..03ddffd988 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/1789] 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 853222642c..abd719b4f5 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/1789] 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 68864c18b6..088236bac5 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 ced1959085..999c7169a3 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 0000000000..774377f400 --- /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 cd35c77953..876a791352 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 82e09e0bd3..1da8858473 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 da8426c905..5400b1d31a 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 bf67146684..6c51033309 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 7af6660da4..6156b8e1cb 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/1789] 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 6628c7bdb1..17032ec814 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/1789] 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 f54fc37ba4..00874b9081 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 ff45172fd8..02705f32cd 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 178c16b4a8..8cedcd1272 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 9d60af25c5..8f19f7659e 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/1789] 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 01ca997eee..c3fc5050b1 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 c105b2fe64..768d75551f 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 0000000000..d271c7bf52 --- /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/1789] `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 46492450d9..eda76bc797 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/1789] 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 9704d5e82e..130dd0bef8 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/1789] 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 55b58e1a56..15e25f2a80 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 cda1b9fb41..8ee3e675ed 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/1789] `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 eda76bc797..620138e0bc 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/1789] 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 05268af81e..baaef68a8c 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/1789] 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 41d62d1c36..9302b20f69 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/1789] 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 dc30f2afdd..e379c4cc3c 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 0000000000..e0f9353eec --- /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/1789] 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 fd2f2b8ae1..82af7d4267 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 7d28abb0fc..15213c15ea 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 76d3136a2d..24987ddbc9 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/1789] 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 baaef68a8c..2b1df1a790 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/1789] 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 15e25f2a80..73f1cc620f 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 8ee3e675ed..d2adb5606c 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 d242403fb6..4e1cfbcea6 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 f0e25e9296..dfa3218442 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 4002e1ce16..d21d17e739 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/1789] 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 11e845136d..f8ad1af8b3 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 498207dbe7..1662d1fa9a 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 620138e0bc..9891593eb3 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 43f93cb8f5..744265adf6 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 8cedcd1272..a55fd7831d 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 225e65b000..ccda700696 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 7d9c764132..d155be352a 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 491af5b813..c2eea34349 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 700063570e..d0970ba346 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 fe57034af1..672998fdb8 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 6587e85d35..117779e376 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 2a56264475..4bfb46f436 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 1ab8d31eff..79c83c7b0f 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 0000000000..3ce287d614 --- /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 f1ccf3c2a2..23650ed8d3 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 600fa6ca63..b673a3e7eb 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 c822edcc7a..7b9312e6c5 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 4d39431f7b..3de317ba0e 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 c3fc5050b1..74b47dea0b 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 d9bbea57e6..f182372fc0 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 2f58e27d15..30e761b660 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 bbe6d5a73e..d12dda936e 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 6350d17cef..f0e78cf903 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 bd5b2a9082..40ea01b5db 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 6a8d2ab329..72fecc7de6 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 662d2e1b0c..2d0c632298 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 df6bee28b8..9545fff1da 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 0b9d87394a..3ae9434a95 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 45f9cb05f3..a3d8daf1fe 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 7b60d5d3cb..2bd76023f8 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 7427aa167c..9c633d62da 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 ccad08cefb..d675eee45c 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 d29e20a7cc..5c680b5b62 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 0000000000..e0c7aa47e5 --- /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 c80c10d9c0..db046bbe10 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 149caff081..db36561ab6 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 92b0ec286d..d556fff1be 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 632fea85f0..98afacb7d7 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 4364f2374f..df1d0cf060 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 0000000000..df06d98b43 --- /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 5fd6f7e358..7056fc5901 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 13b0fee7de..c839779a55 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 9fcd1e1895..c49cf7823c 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 76855da1f4..e6355875ca 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 8ca9c69c29..7cf3659a50 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 6c5064f4ea..6562ba2005 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 f0bef45903..6f4cbf462b 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 47b0d2477c..988b2a2405 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 4353c6efdd..18b3d719c1 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 abd719b4f5..d6c1463c81 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 aea1961e32..5434782186 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 efdd404424..b205b1d0be 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 4fa4539356..1c01239587 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 05684938b8..341a4069d4 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 8fbed765a9..9050c74c15 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 a38116b682..8ffb1ddb89 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 93bf8949d9..c30fbdac80 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 9c3296c3d8..e98bc06a76 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 5400b1d31a..cd831db4d8 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 4127dd5870..c5fd9fc1d8 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 3f0c76f250..ebdbd8054d 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 0000000000..2ddf808da2 --- /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 0000000000..7045582dc4 --- /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 0000000000..0c24268faf --- /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 0000000000..fbd7a3eaf7 --- /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 0000000000..de7500ee8c --- /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 036d2b0f1f..2098a1f02b 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 2e2bcf976c..cd278837d6 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 7c9bcc0722..0b5c5c0818 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 55dc30542b..0e125a4a81 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 eebdb08014..ee4e6b55c7 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/1789] 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 e0c7aa47e5..caabc319bf 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/1789] Regenerate baseline --- phpstan-baseline.neon | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 141752f21b..3b47d2e5fc 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/1789] 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 0ea6df31ec..ba198a0c91 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/1789] 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 7bfdf37f9b..59cbedb60c 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 0000000000..07163ecaa1 --- /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 57cb091e19..f912e0d78e 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/1789] 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 40ea01b5db..d086024868 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 248d994266..9d8e735f35 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 354067f0a9..535928c045 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 5054186320..00aa87fc69 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 3ae615b2d9..a7246dd043 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 275bc3774e..c88d354382 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 1f60b4089f..a845bf1d2a 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 83c10f3225..a2e86ffbec 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 623fea93af..061c953a28 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 4f28696fb9..1b283a7990 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 fe49aa8a2d..46d5b728b8 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 7c13500af2..dfe47aa39b 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 28580c448c..abe5331fc6 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 873ad66b6d..80f10030e8 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 3c85802e73..23a2d0930d 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 3627b8a409..a774b4eba4 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 2d70eb4bab..49bb179309 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 1ab4badbe5..f7513c6045 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 a6c4397893..b28f31549b 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 73dff935f2..8d32598294 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/1789] 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 62c022c8e1..997cfea752 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 0000000000..ca34261bc8 --- /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/1789] 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 3b47d2e5fc..d3e9a54a9d 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 a51539df05..3290fb97c7 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 1505fa1bb2..5d7627b306 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/1789] 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 b8dcee4fd8..61f16a5119 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 a14c9d108d..bf89cbdf48 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 0000000000..a85c6c4a42 --- /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 b205234dc2..7331e21bc1 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 0000000000..b1563e2eb1 --- /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 64a4b89c0f..05be45a380 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 0000000000..c9424720d1 --- /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/1789] 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 8d77cb01b4..68a513f295 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/1789] 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 68a513f295..8d77cb01b4 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/1789] 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 8e06b22fda..7227e369b3 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 b89a6dbddf..fd3835fbf1 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 4679d5f31c..ba4bbb2f62 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 6c7f9c40e6..f73011dcad 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 0000000000..640c73a440 --- /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 0000000000..77b64c3a30 --- /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 0000000000..a14174777b --- /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 0000000000..91e9f1f686 --- /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 0000000000..4fdc546062 --- /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 0000000000..9e7c5da4d2 --- /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/1789] `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 99f4287dfe..74b43d80c9 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 79d34fb54f..ac5b757991 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 61f16a5119..e7afd055e4 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 31cdfadb78..849fbfac11 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 0000000000..dddd414b72 --- /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 0000000000..6880568c93 --- /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/1789] 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 130dd0bef8..d320c24d60 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 657c47bd75..43b9091daf 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 0000000000..c08dccb9ea --- /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/1789] Remove sha256 definition --- resources/functionMap.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 9891593eb3..611ac517ec 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/1789] 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 a85c6c4a42..7bdc70e3bf 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 499b62e154..a4d91a9ba2 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 9c8181f4a3..8139960e66 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/1789] 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 73f1cc620f..71c48bfbc7 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 d2adb5606c..6a7f1ff905 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 71c48bfbc7..8963aa75c3 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 6a7f1ff905..a1f8f8e83a 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8963aa75c3..988292f2d6 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 a1f8f8e83a..d705cd728f 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/1789] 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 988292f2d6..da38b8d286 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 d705cd728f..569d899634 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/1789] 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 957cfe721d..0d0008d341 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index da38b8d286..593163649e 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 569d899634..a31b6c1e33 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/1789] 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 a4758648c6..10f30a8d48 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 131376d66d..744c857f8f 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/1789] 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 10f30a8d48..a4758648c6 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 744c857f8f..131376d66d 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/1789] 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 131376d66d..6f00bc8031 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 593163649e..684e819342 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 a31b6c1e33..1f3a161391 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/1789] 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 6f00bc8031..6d9dab1062 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/1789] 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 684e819342..1505dfeb90 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 1f3a161391..b40729b153 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/1789] 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 72fecc7de6..d41b79e951 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 a80e8b066d..26640a5141 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 ff9f5be5ae..4c87845d24 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 0000000000..6f7f8d83d8 --- /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/1789] 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 26640a5141..40d94efff3 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/1789] 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 1505dfeb90..a9260b897c 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 b40729b153..1135a6a574 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index a9260b897c..912996ab0d 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 1135a6a574..7441a9d501 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 912996ab0d..0f28344835 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 7441a9d501..7cd32ee1ad 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/1789] 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 4680ddb82d..86ee004f07 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 3282ee2c4e..be44969c3c 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 21228704f5..12de9af1d3 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 d4927a56c4..8c2a23a9d4 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 3f500b62c1..c8602aef79 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 8520f6488d..e636945cc2 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 d9f62fa618..df5354adb3 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 0000000000..3500959541 --- /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 ecf4597d97..de425931da 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 0000000000..0b2ca5ba09 --- /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 0000000000..d035d36810 --- /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 0000000000..b34e66a886 --- /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 0000000000..dc839f0d2c --- /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/1789] 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 f8ad1af8b3..19b6388858 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 0000000000..35999b424b --- /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 0000000000..345d54dbd3 --- /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/1789] 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 6f4cbf462b..14eb812267 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/1789] 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 3eba789175..623e3c0bcd 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 64ecbdeb05..4c7ddbc2b0 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/1789] 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 2d4dea0dbd..9753863557 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 8111e9a965..65e4714487 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 77b64c3a30..e4708c5f4c 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 83b0f40567..0ac6304231 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 8c0105a424..2fc8264821 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 0000000000..bd0bf858c3 --- /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/1789] 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 d41b79e951..519649a384 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/1789] `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 3ae9434a95..6ca4e4bad8 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 5434782186..42b0d24959 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 62350f5992..d53ab61f00 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 2455d6ace9..3e3cedbbff 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 0b7058de01..0f14074f5c 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 b8f0e6793d..be439d0427 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 2f02f1e733..b6a6eb6c66 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 a7cdc6540a..9f95a09c0a 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 bddbb8f06d..0588be365b 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/1789] 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 0f28344835..5e0a1f1e01 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 7cd32ee1ad..22c9a79b6e 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/1789] 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 82af7d4267..703d345806 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 af3d4af2c3..89017fe2cf 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 24987ddbc9..bc5b8f6cea 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/1789] 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 5e0a1f1e01..c678b5998f 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 22c9a79b6e..8557d36dae 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/1789] 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 bb267a5e49..879dc310e4 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 0000000000..8a38465040 --- /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 0000000000..fccf29931c --- /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/1789] 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 7ec0a21da2..fa9198745f 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/1789] 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 623e3c0bcd..d1dc17f176 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 4c7ddbc2b0..038b36f7fd 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/1789] 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 083914085a..893627aae2 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 cd831db4d8..59d1af3931 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 c5fd9fc1d8..744bdaf3b5 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/1789] 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 6bd3e1dfd7..6bc5dc45c2 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 0000000000..d64dcc6ebe --- /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/1789] 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 6bc5dc45c2..732819b506 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/1789] 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 5c4d86526e..5f518077a2 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/1789] 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 ebdf3a03e4..a4ee70e41f 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 0000000000..592f0af752 --- /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/1789] 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 ce29b37d2a..31c239af58 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 d986969c3e..2918ebeb89 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 eaa0465a42..13082d79b0 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 0000000000..29334bf835 --- /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/1789] 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 e79a889dd7..145756b971 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/1789] 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 3282ee2c4e..11807088ca 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 1382d99ee1..15f1048b86 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 0000000000..b10d2d0080 --- /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 0000000000..1a8fda4bf6 --- /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 0000000000..fdf86f77f1 --- /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/1789] 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 e145e4b530..f0ebf3be85 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 b507b05ae6..b3ff058c11 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/1789] 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 e046f89750..d7c87c8ecc 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 11586e3097..cc0ae09a97 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 07a7a574e1..cd785677b1 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 7546993297..914d0a43f1 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 0dfb5d71ec..13c8bfe3a2 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 f1b9181923..ec5b1dc13b 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 36c609811b..792f4428f0 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 0a86ea9d2c..0e721b0497 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/1789] 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 dba7fcfe4e..946e492aa7 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 0000000000..72a268c506 --- /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/1789] 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 946e492aa7..75db1668c7 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 0000000000..165b61b63e --- /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/1789] 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 0000000000..e92ce7da4e --- /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/1789] 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 75db1668c7..c818426111 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 0000000000..c494d8d60d --- /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/1789] 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 e2fcdaf7e6..9543cf7cd3 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/1789] 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 9543cf7cd3..b49b97178c 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index c678b5998f..166ac44388 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 8557d36dae..f619dbd9c9 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/1789] 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 9b382375c3..40e57d400d 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 f73011dcad..3dbe4e87ec 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/1789] 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 611ac517ec..d21c346e0e 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/1789] 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 8021baae62..0badf2930c 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/1789] 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 166ac44388..8670cde3ee 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 f619dbd9c9..265045a14f 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/1789] 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 623b593f5f..69c8467515 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/1789] 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 9c633d62da..fab7c056b0 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 2838f2cbd9..6699adb5f7 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 0000000000..f57bd14122 --- /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/1789] 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 5659a107bc..52ae676397 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/1789] 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 d21c346e0e..1b8e4c28a4 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/1789] 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 c818426111..a98fb20b42 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 0000000000..cb6d424567 --- /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/1789] 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 a98fb20b42..beea95bce0 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 cb6d424567..4d065367a2 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/1789] 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 e04381033e..6976c7d049 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/1789] 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 ad6075e3bf..d8566bfdb0 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 3964727b8d..c84cf8f5f7 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 afe9786db0..190be4331b 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 0000000000..2b9fa768d0 --- /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 1d72b629a8..18e7c7a1fd 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 0000000000..293d9e5e41 --- /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 0000000000..a3be5ead04 --- /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 0000000000..32b09d38d3 --- /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 0000000000..da7d088f1a --- /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 0000000000..67906a2dfe --- /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/1789] 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 aa143cc3af..832c73b1c0 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 7bdc70e3bf..74474b28b0 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 c39da64ce1..50371ab23c 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 a49a650286..ff1be83109 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 0000000000..bf13358857 --- /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 18e7c7a1fd..d4a9dc96d5 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 d5787f8344..64b9783877 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 4907d1d7ec..5286e079fc 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 43b9091daf..e4155ce55b 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 52428ced2b..d61265e3e7 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 af9266b7e5..1be1cfae69 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 31a92a4f92..a8ffcaf800 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 89017fe2cf..a6513f03cb 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 f4eb4c2c6e..ccff7cb19d 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 dbab699a2b..41cc608457 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 6d028b1b96..1961e8e81c 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 842d0513a1..678738ac50 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 bd20b469b8..0da6911a43 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 80d61e299e..51210125cf 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 ef5ca25aef..c87aabde90 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 0000000000..174fef244d --- /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 02b75d1007..6f4a6121ec 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/1789] 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 beea95bce0..a8a9755e8a 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 0000000000..4a956e42a4 --- /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/1789] 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 a3be5ead04..fb35b438a0 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/1789] 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 b849f968aa..67af9eb5e0 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 841ae2f46c..fc206e19d4 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 153c222091..9c5ad24d73 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 fefc7bac89..433931baf3 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/1789] 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 832c73b1c0..f883c95578 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 74474b28b0..229dccb72d 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 0000000000..fac3b8a066 --- /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 0000000000..56eba41c9a --- /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/1789] 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 7227e369b3..76dd0d8904 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 40e57d400d..d77e889531 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 3dbe4e87ec..f7328f7863 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 e42a60d5f7..8b38392470 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 c019d69240..ed9639e41f 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 4fbe76d20d..628041a032 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 b6530920df..beb402c267 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 0d033268d8..38a555afda 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 8670cde3ee..30dfc158c3 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 265045a14f..731974732a 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/1789] 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 7700ceb911..24ba44284b 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 c3d3488fea..3165350af2 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 7c2f57188d..34ef71bb83 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 320b4eba1b..396be1c0be 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/1789] 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 52d4cc8cc6..541d15addc 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 ab5f7c28d8..0c6904033b 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 33a3db908b..aaa121a682 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 e586a82aa2..185fc779b4 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 96505cbf9c..872d536314 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 86ee004f07..d93e59843f 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 adea89e232..0cf91034a3 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 ca9f6bd246..5aea839c83 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 b7f4249e8b..d34bbec06a 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 161309d17c..6c71c8d1f0 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 97cf5b30da..d7c4673b40 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 30dfc158c3..df3cf4dd96 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 731974732a..b5b530e613 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/1789] 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 f00b6ac922..0ac13c5f68 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/1789] 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 e636945cc2..98f86eac4d 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 31ef751715..4eb3bd50fd 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 b4757aaac2..f458512622 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 aad0f3ef5b..e91a274257 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 d95fecab11..9e00dd0f65 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 bba8a89f20..a3ca84cf64 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 92bec36518..cc3eba83f3 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 e0bbf466ab..c4a819d1fc 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/1789] 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 c4b8b0b364..97edeb39ba 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 08f4790001..f31e3108b4 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 f9ae03d0ac..a034eea321 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 e99bb4cbaa..a46eefc807 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 402c985b0e..9570359eaf 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 df059481e6..0b0eb798ec 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 255f56dd7a..b79f6ed7ea 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 c69888cf30..9156bc09b7 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 cc3eba83f3..16c414170b 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 c4a819d1fc..6e56f9dfcd 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 0000000000..c8656f5da2 --- /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/1789] 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 df3cf4dd96..6e2c1cb8c4 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 b5b530e613..880e113451 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/1789] 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 a70c0ca5ec..7aa9791928 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 0000000000..8217fa1ef4 --- /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 54555706ee..cd7ca830b3 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 2ce5cc63cf..7c250f0cf5 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 e8fbc2e824..ea6764ec2e 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 d32c16e75e..888530f270 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 85d863ba81..f9194090d3 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 2a94fc4da5..6332238c33 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 75fbfa4527..ff57846f96 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 0000000000..ce520ef7aa --- /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 0000000000..6de5cc39d6 --- /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 0000000000..a7325b8fc3 --- /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/1789] 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 d8566bfdb0..407333c955 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 22434786a8..6472285f76 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 de80ea8f95..2ce3e7e268 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 0000000000..e0933face1 --- /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/1789] 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 7aa9791928..c9a73a68e4 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 0b2ca5ba09..f6fde1e51c 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 dc839f0d2c..fb0edcc3ce 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/1789] 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 f883c95578..bfa07d782d 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 c9a73a68e4..4dd117aab8 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 0000000000..0484c2568f --- /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 1d157476df..809e32f888 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 8cd703c8b2..52ab304f98 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 0000000000..b169e7e7a3 --- /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 94f0b0ffeb..17aa83b245 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 0000000000..1fc6f744b2 --- /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/1789] 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 d77e889531..1501a7a253 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 4dd117aab8..98de22733b 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 0000000000..5a49a70915 --- /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 8fbd112742..71bab19964 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 9a14e3aec4..3cc5e6c62e 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 b169e7e7a3..0e49e0e981 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 835486fdc2..e0afccabb7 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/1789] 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 52ab304f98..af7d9a80f4 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 9f851ce9c6..58abdef6a6 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 1fd11fe100..c524382174 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 0000000000..206298551e --- /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/1789] 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 250d545f15..31e065bf6f 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 0484c2568f..b27899949d 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 0000000000..1651ddfdcd --- /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 0000000000..0f4190b433 --- /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 0000000000..9271b944bc --- /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/1789] 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 98de22733b..0056af9f3f 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 dede3e9211..061a8af510 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 0000000000..0e305bf104 --- /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/1789] 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 7650418141..0a5bb7eff3 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/1789] 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 b41185b5c9..137bd8f00e 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 0a5bb7eff3..c56a986f1c 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 0000000000..6b856eb132 --- /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/1789] 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 0056af9f3f..7ad28508ac 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 0000000000..301d8359e4 --- /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 9a3e64e3ac..ef53fa16c8 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 79eb736c16..9c170899f2 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 0000000000..eff880e46c --- /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/1789] 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 301d8359e4..34bdbcfd31 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 78b822ca3a..e6747a153c 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 c4715616e7..cc431c7a5b 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 55addb6d90..a98855249a 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 fbeac4f3a7..85b16a86d6 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 ef9ea9a2be..c9540c7b64 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 c42863737c..333f88bd33 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 888530f270..6209a57bc8 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 28e559719a..3926a39789 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 4073809a23..a06da4df47 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 ae1e86fe8c..6013bcfa3b 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 8e8447ecac..43435261f6 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 0db311786e..40988deb29 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 91964e8eda..0f1c6c5162 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 9b941088bc..1469cd3f43 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 a05182fa66..b74c62975a 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 1651ddfdcd..cf30777b19 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 ca96ebae94..7c05525aae 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 0e49e0e981..cc143f855d 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 d798e65b66..7058b8b510 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 0f4190b433..8a318db7ed 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/1789] 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 3926a39789..babe91bb3b 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 f043cf3c3e..50d1502401 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 061a8af510..dd5c31608d 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 0000000000..d25a9f8b4b --- /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/1789] 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 babe91bb3b..2879f2a9ea 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 cc143f855d..4a7f0d9f3a 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/1789] 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 2879f2a9ea..d8ce00cdf6 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 fb64553a3b..3dd095b13f 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 0000000000..06953a9661 --- /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/1789] 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 d8ce00cdf6..e9aaa764bc 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 f3ba064bac..0857934cb5 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 0000000000..9f695e4573 --- /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/1789] 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 dd5c31608d..b374da4943 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 d25a9f8b4b..6c6872df24 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/1789] 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 407333c955..8a8077a3e1 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 12de9af1d3..03c717dfff 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 773c36f6e4..eb9492f3cd 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 e0afccabb7..8dbf569171 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 0000000000..105bcf5d76 --- /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 0000000000..7b73ef3d09 --- /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/1789] 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 c71ce58bbc..29d913d779 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 70d5379701..c7ec6ed0ad 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 0000000000..179dfdde04 --- /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/1789] 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 7ad28508ac..53f0f97805 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 0000000000..7d97a140b9 --- /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/1789] 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 31e065bf6f..6522c1eb5b 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 0000000000..67f8f134bb --- /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 0000000000..bdfcaf5f27 --- /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 0000000000..ffc0e559f6 --- /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/1789] 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 6522c1eb5b..b7d1a4c15e 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 0000000000..61b1c32a6c --- /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 0000000000..8eedc78214 --- /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 0000000000..077792c406 --- /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/1789] 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 92b6429eb0..a3b8f1db9e 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 0000000000..f902fa9f00 --- /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/1789] 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 bfa07d782d..5bd3f0a86e 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 53f0f97805..1639a12522 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 9d7d9aa5bf..f1587ef242 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 ff57846f96..8daa5fc1ee 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 ce520ef7aa..337713a18a 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 0000000000..3caf94adf3 --- /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/1789] 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 1639a12522..3bc323341c 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 f1587ef242..eb64cacdbb 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 fc8ad21c17..9fef24a587 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 4a7f0d9f3a..8e32e4c96d 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 8daa5fc1ee..be18a8fb4b 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 337713a18a..efdfaece70 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 3caf94adf3..0da2fbe4e5 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/1789] 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 8a8077a3e1..fc4a0fe42e 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 75657f232f..4f421e5a6c 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 493e23592b..bca307593c 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 0000000000..2cc1ba297b --- /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/1789] 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 c84cf8f5f7..1a46352922 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 0000000000..bd4968e8bf --- /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 0000000000..5f627c1902 --- /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 0000000000..495cc793b0 --- /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/1789] 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 3bc323341c..7339f0a042 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 08f4885067..33a117fd17 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 834bac42ff..6eacf1535d 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 b4c12e3877..0300b6ecc8 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 73b9f59106..300aad76b2 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 1c16fec53d..cad8c5348c 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 0000000000..6d47003630 --- /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 0000000000..547a47eece --- /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/1789] 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 bda46632c2..b026238cfb 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 0000000000..00ed4bacd4 --- /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 0000000000..0c3d0f75a1 --- /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 0000000000..92998bafa4 --- /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/1789] 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 b7d1a4c15e..c946a5ee3f 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 0000000000..71b7cd9c2d --- /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 0000000000..fecb9cfdc5 --- /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 0000000000..82c4c2381f --- /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/1789] 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 1501a7a253..ec1c876661 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 0000000000..d9b7a6b864 --- /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 0000000000..e7f6130d67 --- /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 0000000000..d9fba8d0f1 --- /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/1789] 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 087c89b6ed..33a2e120c3 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 2328aeb0d7..e378f873b6 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 0000000000..c40b13aa9f --- /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/1789] 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 51b22dd564..c9e27ca74a 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 2caa53394e..5e99af64f5 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 c664e1658a..e91e647054 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 0047c107ed..be63bff8e2 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 0000000000..f145c5d437 --- /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 0000000000..1221fe7b43 --- /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/1789] 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 af0107c2a6..d7a005c589 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 0000000000..b59eb1dc3e --- /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/1789] 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 ec1c876661..4d7c3b4e99 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 39ebcf09b3..33fde1e46e 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 0000000000..56c0ac529e --- /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 acdbeef79f..47b3a78248 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 ace29c0b95..9c9c5965ef 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/1789] 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 2d547cb94e..9cd92e09e7 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 0000000000..dffebfa1c8 --- /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 0000000000..b0d4d718ad --- /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 0000000000..b1ce3b8762 --- /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/1789] 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 fc4a0fe42e..bc702f32d4 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 1a46352922..ff1a67c728 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 6874582743..700f6e7b71 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 0000000000..be668710f6 --- /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 0000000000..cab45fe36a --- /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 0000000000..a818f22c1e --- /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/1789] 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 7339f0a042..495879eaea 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 b27899949d..99de6b73a0 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 7d97a140b9..42db85ee6d 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 67f8f134bb..aeedaeb4a9 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/1789] 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 51bddcaabe..4f6b640785 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 db8df05ab8..aff5f65822 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 7e1388bf5a..90c653484b 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 91238c18b9..43151a7a7f 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 8ebb272bfd..f9bdddc13e 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 40b28e9ff6..c4c2713c4b 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/1789] 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 bc702f32d4..1452d74f71 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 ff1a67c728..fc3bfc84f2 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 0000000000..941dd84973 --- /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 0000000000..76e7b06b8c --- /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 0000000000..a8279832b2 --- /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/1789] 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 fc3bfc84f2..6493abd868 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 941dd84973..e8de30667e 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 76e7b06b8c..0b879f0ad5 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 a8279832b2..12c82ddc0a 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/1789] 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 67a03643f9..a5a5c16c81 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 acbfca290c..7060aeecea 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 2ee62db90b..9abc02950f 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/1789] 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 495879eaea..791a8920b7 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/1789] 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 6493abd868..980324fc54 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 4d7c3b4e99..b2d222ebb3 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 0000000000..8609cba9d9 --- /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 5d7a9abcc4..80bc39e4ac 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 773c715d04..9e2d8852be 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 b6ea917903..dd44445971 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 164aefaafe..db82d7fcf1 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/1789] 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 30f7233614..11ac4de1a4 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/1789] 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 980324fc54..dbb2b4836c 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 5bd3f0a86e..4a0169048a 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 925e35a50e..a2215bc25c 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 98f86eac4d..1f7c05a50c 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 e6747a153c..9188ef7721 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 e1c62c60ca..9eeb979821 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 cd7ca830b3..909a6b50b9 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 cc431c7a5b..07dc20ce68 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 a98855249a..40a48911e8 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 85b16a86d6..c4a55163bb 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 c9540c7b64..8a9a4eed28 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 e9aaa764bc..1fa95c67aa 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 a06da4df47..a8cfff2cb3 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 6013bcfa3b..3382a49344 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 43435261f6..d5ffc248c6 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 40988deb29..b2d1551e5c 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 0f1c6c5162..bc3c4f4411 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 1469cd3f43..52e1571309 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 8609cba9d9..023cc16756 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 80bc39e4ac..6577820611 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 9e2d8852be..e9b382c7f2 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 a5a5c16c81..94e526da0c 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 b74c62975a..36a286a4a0 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 0000000000..f6d3cc0cd0 --- /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 981415e915..52637bb509 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 29d913d779..70f18bcbcc 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 11ac4de1a4..d5079dc353 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 2b5c7d010a..4e9673070f 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 2d2ab20f6a..a3ce3325f0 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 bfe8b1f7bf..137caf24ce 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 7c05525aae..37a98fa9ba 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 d58eeb7217..35d8075f8b 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 be18a8fb4b..48c4197868 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 6df44ad440..77b9d3e008 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 b734fac05c..d7af0d248f 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 0b57c66a84..9f0780f113 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 dd44445971..cd318cfedc 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 db82d7fcf1..1b79507bad 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 0000000000..8bfcf37bd3 --- /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 c0dae45d97..e8acef73f8 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 90da9c44ec..966f8e9e41 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 0000000000..8163bb9f00 --- /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 0000000000..eaa2de7ec8 --- /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 0000000000..ee38e072ce --- /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 0000000000..6ea2ab154b --- /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/1789] 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 1fa95c67aa..3ff948eb3a 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 5767635e27..61325ed508 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 dafac4e5f0..954eaae511 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 0000000000..662f285a7e --- /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/1789] 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 662f285a7e..a5d78f27bc 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/1789] 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 1452d74f71..7ac68874b1 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 a5d78f27bc..89ed23c6db 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/1789] 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 8bfcf37bd3..7f24e6f628 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 eaa2de7ec8..1b49683a01 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/1789] 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 89ed23c6db..02d7467e57 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/1789] 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 74b43d80c9..22f709cbc9 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 6d8608ec18..8d8daa714f 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 ac5b757991..657cb8c865 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/1789] 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 cd318cfedc..53f852ae8e 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 6ea2ab154b..a6273caf09 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/1789] 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 791a8920b7..fb72cfeb6b 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 e0c8cb0af9..603b7d6f2f 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 0000000000..36aa85b6bb --- /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 da076db1c7..ec97b0481a 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 0000000000..0e9ab15119 --- /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/1789] 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 f31e3108b4..d630cad147 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 04fd4fb60e..faac90150e 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 e0f4964c93..9429c7ea73 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 4605c92efe..eeedd4bc68 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 9e00dd0f65..cc5b0f4eb4 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 a3ca84cf64..3c12092eb7 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 16c414170b..243b7672fd 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/1789] 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 603b7d6f2f..3e2c72e29b 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/1789] 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 61b1c32a6c..9a2fc917e7 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 077792c406..76ceabe408 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/1789] 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 6e2c1cb8c4..6ac10b2742 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 880e113451..039bd64a30 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/1789] 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 af7d9a80f4..9e36a632ae 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/1789] 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 3ff948eb3a..1284d4a699 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 53f852ae8e..d1e43fd0e1 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 7f24e6f628..3d78abbee9 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/1789] 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 fb72cfeb6b..eb05b36f3b 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 0000000000..53f3f3f50f --- /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/1789] 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 e7f6130d67..cf01fb3852 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 fecb9cfdc5..6072db088b 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 0c3d0f75a1..6fed25e6c2 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 d9fba8d0f1..773f849d74 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 82c4c2381f..08e3f10940 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 92998bafa4..6cf4b55072 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 ffc0e559f6..56133fb556 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/1789] 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 c946a5ee3f..4e5f80c5ef 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 cf30777b19..0000000000 --- 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 c524382174..98bb11b0b5 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 8a318db7ed..0000000000 --- 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/1789] 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 b2d222ebb3..c3c2c7bded 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 eb05b36f3b..ab61ff1486 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 eb9492f3cd..80f5b2f594 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 5a49a70915..0000000000 --- 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 71bab19964..8fbd112742 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 eb64cacdbb..650408f349 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 3cc5e6c62e..614ef44910 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 8dbf569171..69c0e8af10 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/1789] 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 c3c2c7bded..b9c52c9a57 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 33fde1e46e..0374aa268f 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 ab8553b5cc..16226a1074 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 0000000000..4807453f2a --- /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 38c588f9db..81a9b1b0e1 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 28b0091af9..adcb69cdbe 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 5f75cd0554..580d21787c 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 abbe2927ab..9c65a99d35 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 0000000000..eacb78bbf3 --- /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/1789] 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 1514781d0f..114843f993 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 243b7672fd..415cc07a73 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/1789] 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 114843f993..7659db1e60 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 b047b86a69..5697dee6b2 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/1789] 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 229dccb72d..96bf233209 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 50371ab23c..bd637913bc 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 a6513f03cb..2e8afa35a4 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 0000000000..8172892d95 --- /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 0000000000..29d9ac8b4e --- /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/1789] 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 7659db1e60..114843f993 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 5697dee6b2..b047b86a69 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/1789] 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 b90f4d31a7..1c956015dd 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 415cc07a73..c385548cf5 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 6e56f9dfcd..e49a6100f7 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/1789] 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 2e8afa35a4..7fde83eb12 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/1789] 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 ab61ff1486..f8a09a5c4e 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/1789] 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 52ae676397..9f726ede71 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 0000000000..a486eda5c0 --- /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 80f10030e8..6f69f3b78f 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/1789] 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 1662d1fa9a..1011e39a00 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 5291652129..8c158da87b 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 8560faf5a9..762e577211 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/1789] 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 0000000000..89934bb502 --- /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 0000000000..12ed0edfd0 --- /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 98bb11b0b5..73350821e1 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 0000000000..b9c479000a --- /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 48258202dc..1fa91d3fe6 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 0000000000..759c3b5bb5 --- /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 4ec5028172..64ee811d81 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 0000000000..ab5d44154a --- /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/1789] 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 1011e39a00..9977a1992d 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 5b177a9f68..01d2143b25 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 a52f640ef2..f0d86e61b6 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 a89705028b..a3ffa071b7 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 0000000000..8f452e90f0 --- /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 13082d79b0..98e54bce0e 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 0000000000..269bbd5a87 --- /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 0000000000..17c2aa50f6 --- /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/1789] 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 89934bb502..cb9103eae0 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/1789] 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 2f9743d575..d4072a4170 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 0000000000..28f5369952 --- /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 0000000000..4ef6334828 --- /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 0000000000..10455d131d --- /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/1789] 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 1fa91d3fe6..ff465ea7e5 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/1789] 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 0000000000..10a7af56f1 --- /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 0000000000..f95e1b5573 --- /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 0000000000..da1ea8ad08 --- /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/1789] 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 dbb2b4836c..9160281651 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 b9c52c9a57..9d62f32c60 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/1789] 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 10a7af56f1..cee351e3ff 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 f95e1b5573..e97673ff5f 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/1789] 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 6ac10b2742..6edf082303 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 039bd64a30..f9f801df4c 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/1789] 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 9f726ede71..274d8392e4 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 5f518077a2..c0617856f7 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 a05f550105..f105115df7 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 2dca4eb85e..47b2feb4df 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 1ec55619b6..675d150d52 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 e70d2eb292..56c78e874b 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 622a093e0b..e05a0be902 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 5dd972f709..8ef6f87c66 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 77feb89d7d..67f6f42b4e 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 0000000000..f8dc4ede85 --- /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/1789] 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 9977a1992d..93649e9087 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 5c680b5b62..a052a43416 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 0000000000..48a49258cc --- /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/1789] 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 872d536314..5b77182b86 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 6edf082303..4a524d5ff7 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 0000000000..1f34c8dfd2 --- /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 0000000000..f2f9c41148 --- /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 0000000000..81b6332f96 --- /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 0000000000..11aac2512f --- /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/1789] 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 5b77182b86..208df4952f 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 4a524d5ff7..6edf082303 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 0000000000..61ead86667 --- /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 0000000000..a072011fe8 --- /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 0000000000..b383d88ac5 --- /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/1789] 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 297697ee78..3bd6db7556 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 58b6f34df5..e91ca6748e 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 9188ef7721..cc1994bc9a 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 07dc20ce68..f235b2e6e3 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 40a48911e8..c2c6d4c768 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 c4a55163bb..63b6246dd3 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 8a9a4eed28..a74bb419ff 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 a8cfff2cb3..d354ae5fe2 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 3382a49344..0a2f8faf35 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 d5ffc248c6..e964d99b5e 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 151945e921..06069f8410 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 4b843829ad..18beaf3f8e 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 b2d1551e5c..9976bab57d 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 bc3c4f4411..24e2e91156 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 52e1571309..fe64beb0f0 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 36a286a4a0..19e77db7e0 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 37a98fa9ba..d5fb99f546 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 0000000000..5410dc2150 --- /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/1789] 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 9033aa3865..29b0801ece 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 c4000b9aff..4d062efd56 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 2d52ee99e1..0ca6cf4e76 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 999c7169a3..4ad6a7cec4 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 16529f3a74..b15cf528b0 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 0000000000..f0f21220d1 --- /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/1789] 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 cfccf5b972..f52b6a9c2b 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/1789] 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 793015d51c..a0d08561d7 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/1789] 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 6c71c8d1f0..602152e12f 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/1789] 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 0b0eb798ec..0e26b52a67 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 73bdfa483f..84464f30bd 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 e50dc20676..40b1c85587 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 f8f70e07d0..02386aa4b7 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 202e5b1700..17890a823c 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 51e121a4c1..16f060465c 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/1789] 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 1b8e4c28a4..90fcb6e6b4 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/1789] 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 93649e9087..e6e26f8120 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 088236bac5..f7d01f2da6 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 0000000000..1a2c839c05 --- /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 dc2bcaa2f3..22c58c4b20 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 098b74cb47..9ad5cceea7 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 fde28cab1a..5382491c02 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/1789] 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 b15cf528b0..2bfbc1592d 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 0000000000..23cd280d7a --- /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 0000000000..b89b47ba6d --- /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/1789] 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 f64ac08dd7..278c979a89 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/1789] 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 fab7c056b0..96d30d0958 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 0000000000..5566a2a2da --- /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/1789] 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 2bfbc1592d..896173a6ee 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/1789] 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 19b6388858..a605093fcb 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 b2508600f4..6e3c75b9a1 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 0000000000..391974231d --- /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 42b0d24959..64bb226b0f 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 0000000000..aef6c0ac75 --- /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 28d766512f..d8a9be4200 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 0000000000..4b64fae830 --- /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/1789] 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 391974231d..5367744502 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/1789] 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 7dd65488a0..4c0a424c1b 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 e4155ce55b..7852d40555 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 0000000000..bb07d5954a --- /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/1789] 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 114843f993..76fdf42a5d 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/1789] 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 90fcb6e6b4..5148ffeb6d 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 d242a5410a..c6a9dfe91a 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 925873293d..85ba080957 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 0d068d3765..2ffcb920f8 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 fe8cd726f0..1d11247d53 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 17b9eb4d8c..72f977baae 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/1789] Makefile: Disable xdebug in dev tools --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 11807088ca..a2239eba00 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/1789] shell_exec --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b6caed90bd..f2b70427a4 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/1789] 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 03ddffd988..cd776efa5f 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 0000000000..f67d1aa3cb --- /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/1789] 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 a605093fcb..205827901e 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 0000000000..5d24f86f52 --- /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 0000000000..91e178514c --- /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/1789] 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 07c9488783..bd13fe0612 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 1f7c05a50c..651aff5205 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 0000000000..1abd672f68 --- /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 38ab53a124..ed9b43b6be 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 0000000000..1a53ae8750 --- /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 20396c04fc..6a13532d9a 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 0000000000..3955a5013f --- /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 0000000000..00f1b9634f --- /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 38f023a637..85e98f4f8d 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 30539fae85..3be9b7193d 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/1789] 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 1363deb2fe..cc9f6112fc 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/1789] Update Symfony polyfills --- composer.lock | 144 +++++++++++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/composer.lock b/composer.lock index 7a215f2f76..26d8c72dc7 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/1789] 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 e6e26f8120..be237d9d79 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 20128f0a92..5beabcbcf3 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 64bb226b0f..c2f8dbde17 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/1789] 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 3bd6db7556..297697ee78 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 e91ca6748e..58b6f34df5 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 cc1994bc9a..9188ef7721 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 f235b2e6e3..07dc20ce68 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 c2c6d4c768..40a48911e8 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 63b6246dd3..c4a55163bb 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 a74bb419ff..8a9a4eed28 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 d354ae5fe2..a8cfff2cb3 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 0a2f8faf35..3382a49344 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 e964d99b5e..d5ffc248c6 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 06069f8410..151945e921 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 18beaf3f8e..4b843829ad 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 9976bab57d..b2d1551e5c 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 24e2e91156..bc3c4f4411 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 fe64beb0f0..52e1571309 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 19e77db7e0..36a286a4a0 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 d5fb99f546..37a98fa9ba 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 5410dc2150..0000000000 --- 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/1789] 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 896173a6ee..b15cf528b0 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 23cd280d7a..0000000000 --- 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 b89b47ba6d..0000000000 --- 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/1789] 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 29b0801ece..9033aa3865 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 4d062efd56..c4000b9aff 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 0ca6cf4e76..2d52ee99e1 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 4ad6a7cec4..999c7169a3 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 b15cf528b0..16529f3a74 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 f0f21220d1..0000000000 --- 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/1789] 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 297697ee78..3bd6db7556 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 58b6f34df5..e91ca6748e 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 9188ef7721..cc1994bc9a 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 07dc20ce68..f235b2e6e3 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 40a48911e8..c2c6d4c768 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 c4a55163bb..63b6246dd3 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 8a9a4eed28..a74bb419ff 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 a8cfff2cb3..d354ae5fe2 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 3382a49344..0a2f8faf35 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 d5ffc248c6..e964d99b5e 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 151945e921..06069f8410 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 4b843829ad..18beaf3f8e 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 b2d1551e5c..9976bab57d 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 bc3c4f4411..24e2e91156 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 52e1571309..fe64beb0f0 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 36a286a4a0..19e77db7e0 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 37a98fa9ba..d5fb99f546 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 0000000000..5410dc2150 --- /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/1789] 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 6edf082303..ed4e90719f 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 3ffd1f0332..e6942cdfa8 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/1789] 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 e91ca6748e..b6c5798b8d 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 750466a271..5f60fe8eb7 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 967c58d690..9af225a3c1 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 97edeb39ba..5ea351a924 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 d630cad147..961e2cbd95 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 faac90150e..3befd2d478 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 9429c7ea73..447bf76ecc 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 a034eea321..18ee7399bf 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 492bd1ba27..455f0de86e 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 296c5b7308..ec6e822a31 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 a46eefc807..d4726fd5c2 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 70ba480a7e..c43c86a903 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 0e26b52a67..679c0b9824 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 fe6e412ad2..b47dda2339 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 a76aa90596..55aebcadc8 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 a01321c138..ea1c4b09ef 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 880006af90..253af75e4e 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 c26509b017..52c13a9680 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 4eb3bd50fd..e974888bc7 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 b79f6ed7ea..ab9f86d034 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 502fb6330a..e2864494e9 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 c63c78f214..487a474827 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 eab6009abb..518ffa8f4a 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 f52c5ea8de..0b91e093e6 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 7d66fa7e79..fd30ee8bd8 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 ac1f139938..0ad78520f2 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 f62023f2c6..4ed9c79c1c 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 8885972a66..702f3f751e 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 355d186986..0ff25dc124 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 eeedd4bc68..c0114d8462 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 ddc501d022..813b0136d9 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 3c5a4e5b5f..5eb703077f 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 70f7d2c007..45fc20121f 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 0badf2930c..15886a053c 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 9156bc09b7..08d678152a 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 b6bac5908c..83c5a05726 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 5410dc2150..9445d8632b 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/1789] 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 274d8392e4..b95f28ace1 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 0000000000..59d02b403f --- /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/1789] 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 f2b70427a4..df1509bc46 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/1789] 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 988b2a2405..ec733df635 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 6083de943d..e25e8df924 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 0000000000..2d1564036f --- /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/1789] 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 1fc3359bc2..fad4af1662 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/1789] 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 df1509bc46..c8f5cf6c80 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/1789] 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 c22ec65c29..e1f299a60e 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 340c1331d9..d434683e6a 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 0000000000..5e7380d9ce --- /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/1789] 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 61325ed508..45fae00fc9 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 954eaae511..2a363d7ee0 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 0000000000..014228effc --- /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/1789] 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 45fae00fc9..66c0c62e0c 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 2a363d7ee0..44779f2162 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 0000000000..3722477119 --- /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/1789] 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 544fcb1461..a6254957b2 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ed4e90719f..5b977bf42c 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 e6942cdfa8..1efbc63719 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/1789] 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 9b9a9132e1..f08d329483 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 93c6a67b9d..6c07d330ed 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 0000000000..6a89da7862 --- /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/1789] 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 fd75fa5306..f8030003fe 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/1789] 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 f8030003fe..dd3ebd84b3 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 0000000000..8bddb79eca --- /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/1789] 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 5b977bf42c..ba01942197 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 1efbc63719..8d648923db 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/1789] Update LICENSE --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index 7c0f2b7b69..e5f34e607a 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/1789] `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 c8f5cf6c80..0b93513557 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 0da6911a43..8b08b658f5 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 0000000000..572a3e8f4f --- /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/1789] 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 be237d9d79..64c868244d 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 b95f28ace1..0e7b7c328d 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 744265adf6..af83130ac1 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 c9f0d94ac6..37cd59b2dc 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 607a08a37f..18e9ad6b08 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 3f437a0b91..c0a4936bdc 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 4cd7fd8277..0a171835e1 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 3467703921..2f1317e920 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 0000000000..076717de9e --- /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 ec733df635..12b5ecbd15 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 0000000000..81096260d3 --- /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 6c07d330ed..36fa749987 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 49fc0b97d1..dbaf8f68e4 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 98e54bce0e..a798142941 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 a3db4a3445..ed2d79d790 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 0000000000..cd54f09f16 --- /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 0000000000..688556c99b --- /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 ace29c0b95..d8a6576b1a 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 0000000000..b73a87b955 --- /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 a462bda476..119ce10fb9 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 0e125a4a81..d81d4cadc3 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/1789] 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 076717de9e..8b1cd3d057 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/1789] 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 0e72cc9a7c..54f9e5a809 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/1789] 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 18e9ad6b08..18d8ee0257 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 7056fc5901..a28eacfab6 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 81096260d3..49f8df800c 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/1789] 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 18d8ee0257..44ee69782d 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 8b1cd3d057..0af291f50e 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 49f8df800c..129e72147a 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/1789] 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 44ee69782d..cc8c7121ee 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/1789] 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 cc8c7121ee..2e57c4ef07 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/1789] 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 2e57c4ef07..cc8c7121ee 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/1789] 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 e0db4f89f6..3c7d9da4d4 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 c6fa368103..7757cc3dea 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 016235e900..a16b075630 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 defa4910d3..8b68847f77 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/1789] 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 0af291f50e..7d5ef16d6a 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 129e72147a..c7163151f7 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/1789] 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 faa09ee01d..413f5bca73 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/1789] 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 bd13fe0612..b9f6445b08 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 22f709cbc9..f65a599113 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 657cb8c865..1d4154261d 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 27b2f00f45..073a08368c 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 b6c5798b8d..d5fde30813 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 865abdbe04..b7aab264ff 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 cc1994bc9a..ea0d7e2418 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 4f6b640785..b01a6db6ff 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 0000000000..74d6874223 --- /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 0000000000..74a7d0efaa --- /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 72f918f62b..4029d26554 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 cafc201341..6d0452caff 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 909a6b50b9..334311302a 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 3b3279596a..932d100db2 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 f235b2e6e3..0421bb3ff9 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 e38a8740dc..ffc6afe7c9 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 e3dff92c38..4c2efda773 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 c02b5ea370..a67f79e00b 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 c2c6d4c768..133629a45c 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 7c250f0cf5..a84fd205ef 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 b49a71bb1a..03a9a2b2ea 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 aff5f65822..ab50b76bd8 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 63b6246dd3..25f79396a1 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 33b355b844..297e4dd7d3 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 405eea46b1..993bf34b3b 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 e680908c32..35f70bf37d 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 650408f349..e6fb8ab6e5 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 90c653484b..00e2ea1a99 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 730f8c61e9..7668d51f9e 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 731d4973fe..a9ec6063d8 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 703d345806..81bc3a2a99 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 e28f4cd259..ae8292e32d 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 2758c04c27..61ee5d767b 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 a74bb419ff..ca92d9258c 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 76c8e7cf7a..4020bbdc09 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 43151a7a7f..19a917e0a1 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 413f5bca73..a0d4d17471 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 809e32f888..02a5b642af 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 ea6764ec2e..368ac3f471 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 66dfbd59ed..cd0e6fcbf6 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 6209a57bc8..b2b45932a3 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 22028286d3..ec95a2de81 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 f9bdddc13e..f048ea7100 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 c4c2713c4b..01f69e3ccb 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 1284d4a699..c667c6e5e3 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 d354ae5fe2..45438eb3c2 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 0a2f8faf35..0b478d51c5 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 f9194090d3..96795f6427 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 d3dd2aa2f1..d7d2f09acc 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 33b70bafe4..bd7ef46f2b 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 e964d99b5e..797b1ede60 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 6332238c33..766e665115 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 eaca01ec4c..3b9572b61e 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 1e254b94b7..6fce0b017f 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 c19986d71c..f0ce213ad7 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 9976bab57d..d0729a2260 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 167493c0b8..3d8015ddaf 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 24e2e91156..eb2d00aed5 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 c14e51656e..42bc13b430 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 fe64beb0f0..9f4fb2d904 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 19e77db7e0..1b14b785aa 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 849fbfac11..7587ac8d05 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 8018b44181..b40a8ebca0 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 614db4f0c5..da7cbe82c2 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 d5fb99f546..971a96b2f6 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 0a27ee2889..6162106ac5 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 0000000000..99ef6d9495 --- /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 0000000000..f0c22f45ec --- /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 0000000000..fd02812679 --- /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 0000000000..34ec36599f --- /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/1789] 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 99ef6d9495..756ff9e1b1 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/1789] 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 327c0a7be4..87b97215de 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 f95708eda3..d52b95e8e8 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 0000000000..583a3a9f1b --- /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/1789] 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 b0379d18e7..ab0a439c3b 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 df5354adb3..b6a3c7d493 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 3500959541..50e3880013 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 de425931da..dc011f8a82 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 f6fde1e51c..6d61e5c7cc 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 fb0edcc3ce..24238e5c14 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 0000000000..53aa244fa9 --- /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/1789] 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 aa159d23a4..1b9403b7c5 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 0000000000..d81c6e9907 --- /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 e98bc06a76..c8ae927b2d 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/1789] 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 8a994666be..df44e54715 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 0000000000..99223a770e --- /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/1789] 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 36fa749987..54c9f5c4d1 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 0000000000..60047d13a6 --- /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 0000000000..fec3d30001 --- /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 0000000000..1c41c97c37 --- /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/1789] 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 df44e54715..f337950a0f 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 99223a770e..c48ee43697 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/1789] 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 64c868244d..d9980551b2 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 dd3ebd84b3..786a8e2119 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 98366e15ea..5b6aaacc3b 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/1789] 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 786a8e2119..8036cd78ee 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 0000000000..1010ae098b --- /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/1789] 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 376a4bc130..8036cd78ee 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/1789] 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 dbaf8f68e4..1e6befc008 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 0000000000..e7a6ac9ba9 --- /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/1789] 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 6d61e5c7cc..20541e8f19 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 f727d148b3..f97afbb542 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ba01942197..d18d6cb12d 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 8d648923db..f7395599ee 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/1789] 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 df2d5e5ada..7028593fee 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 0000000000..7a61985ff6 --- /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/1789] 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 9ca87d4dcd..3c27adf2bf 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 df49f7cb16..65f2b787bd 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 0000000000..d1f7a0a9cc --- /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 eceaff15f9..f45e84e145 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 0000000000..44ecbb873f --- /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/1789] 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 65f2b787bd..f472eab562 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 f45e84e145..6bc2d8e2d5 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 44ecbb873f..250b7c83a7 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/1789] 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 d18d6cb12d..b35fcf8df8 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 f7395599ee..12057cf7b4 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/1789] 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 7028593fee..e1e77ddd1a 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 0000000000..e1bd8c5f0c --- /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/1789] 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 e1e77ddd1a..76c62869a0 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/1789] 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 073a08368c..0c0af430e8 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 e1f299a60e..ec4dcba592 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 89433d32ca..528f037f8b 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 63cd185b7a..cb68412aa4 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 d434683e6a..22c15b707b 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 0000000000..d4f0950960 --- /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 a3e1ed68a2..a157df7e80 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 0000000000..ea9488ba44 --- /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/1789] 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 ab0a439c3b..d1fd73e1dd 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 c8602aef79..b567aa7f7b 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 50e3880013..661fb52c4c 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 20541e8f19..6918499f75 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 0000000000..4c69f70d60 --- /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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index b35fcf8df8..8b85b7772e 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 12057cf7b4..21017bc41a 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/1789] 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 8b08b658f5..48caf8f4f1 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 0000000000..56860b669d --- /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/1789] 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 0b93513557..c840903d5e 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/1789] 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 5148ffeb6d..ddfde42e34 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/1789] 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 d1fd73e1dd..47ccd330d4 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 b6a3c7d493..3ff9546d35 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 661fb52c4c..f14c9730a0 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 dc011f8a82..3d6dffcb8c 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 6918499f75..54e041e694 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 0000000000..101f4b3b28 --- /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 0000000000..66f45edf78 --- /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/1789] 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 932d100db2..9a309e3189 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 3b9572b61e..d4147c1aeb 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 6fce0b017f..c78a435583 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 0000000000..97c1c94187 --- /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/1789] 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 d4147c1aeb..980a3f293f 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 97c1c94187..f5199523d4 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/1789] 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 ba174feb29..3f49912a36 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 613779b2e6..4340e0bd99 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 a8a9755e8a..69eb455eaf 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 701dc1f049..8861e9f036 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 98a6dc58cb..39902aa01f 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/1789] 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 8b85b7772e..60067e1b50 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 21017bc41a..83f895229a 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/1789] 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 f0e78cf903..7b0f109b7c 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 6fc8eeabd0..449246f707 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 6699adb5f7..aa4d8cd83b 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 0000000000..acd1816675 --- /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/1789] 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 3c27adf2bf..8961bbda44 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 1b9403b7c5..22d4974aab 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 288ba539a2..3369063444 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 0000000000..40d6f5957a --- /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 7d57aea2c0..d1cb994c92 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 1a4d0cbce9..e2c76ab516 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/1789] 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 cb14b900fe..5bfcf976b2 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/1789] 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 3165350af2..8eeb63a4d5 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/1789] 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 5bfcf976b2..8936631496 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/1789] 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 8961bbda44..11a651dc00 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 22d4974aab..975365a341 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 343482358c..a13281d91f 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 40d6f5957a..50c027445c 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/1789] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 60067e1b50..ac477d6fbf 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 83f895229a..c60100716c 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/1789] 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 3dd095b13f..21c70b7adb 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 2b32877d52..f876b3d823 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 0000000000..cfc7187e9c --- /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/1789] Fix --- src/Type/TypeCombinator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index a13281d91f..f0e2f629a5 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/1789] 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 cfc7187e9c..74d56dc0e8 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/1789] 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 66c0c62e0c..6acc3ca7c3 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 44779f2162..deb20e5877 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 0000000000..8339c89787 --- /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/1789] 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 ac477d6fbf..273df9aeb3 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 c60100716c..b97639f176 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/1789] 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 deb20e5877..0df236c85a 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 8339c89787..e2eac6ff7f 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/1789] 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 6acc3ca7c3..c1806565e2 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 0df236c85a..c5f5cd929c 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 014228effc..75f1a6acca 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/1789] 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 02705f32cd..d031cb8816 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/1789] 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 56a831ec94..cd8b16a326 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/1789] 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 cd8b16a326..0c2bbd532c 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/1789] 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 8b38392470..d87581f69c 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 beb402c267..cf547b909c 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 0000000000..3cbf90ed6d --- /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/1789] 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 ddfde42e34..ce6a8a5912 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/1789] 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 ce6a8a5912..e2a6e57208 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/1789] 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 273df9aeb3..213520adfe 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 b97639f176..0745c35da4 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/1789] 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 213520adfe..6dbb056300 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 0745c35da4..1ab1cfb464 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/1789] 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 8ea1d5bb1e..070896a051 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 9e91634634..6543fbae67 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/1789] 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 d5fde30813..1d19ee7565 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 d8d87fd352..77a4f957fe 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 76c62869a0..42dd7ec3bc 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 0000000000..ce75be8225 --- /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/1789] 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 208df4952f..0d90c4116c 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 0000000000..1557144b26 --- /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 0000000000..e69de29bb2 diff --git a/e2e/bug-12606/test.neon b/e2e/bug-12606/test.neon new file mode 100644 index 0000000000..c308dcf542 --- /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 0000000000..e69de29bb2 diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 75c79d6678..c28e08a77c 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/1789] 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 0d90c4116c..5e574eb6d2 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 b9f6445b08..b7e91bd3d9 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 0000000000..de4a392c33 --- /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 0000000000..f8a4e6ebed --- /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 0000000000..8c53510373 --- /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 0000000000..bc04b24e67 --- /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 0000000000..03011e44fc --- /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 0000000000..17283bafe5 --- /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 0000000000..dc7b0dab5a --- /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 0000000000..b52b4f7ef1 --- /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 0000000000..d55c955157 --- /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 b88b3e0d31..56fde0132e 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 570c637092..969154c3c8 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 0000000000..54ff6d2422 --- /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 b40a8ebca0..e48e757bfe 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 6162106ac5..bacd85a967 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/1789] 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 54ff6d2422..3f8b11f432 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/1789] 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 137bd8f00e..0564f2a957 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 c56a986f1c..88b2b619c2 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 0000000000..bcb8ff1958 --- /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/1789] 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 ab3bce12d2..fb0855b792 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 0f8d05a8d8..9836d85bb0 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/1789] `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 0000000000..4d781e7f00 --- /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/1789] 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 71636b8ca0..968b3d75f3 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 d8bb82d141..32086476be 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 0000000000..8441b10819 --- /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/1789] 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 0070554896..0c1d4e1b49 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 a4ee70e41f..a73ff8ec72 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 f3ea56d12f..08bb43b2fd 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 e25e8df924..c4bb1aec92 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 0000000000..08e7e1c710 --- /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/1789] 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 47ccd330d4..1122b4ab0f 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 b567aa7f7b..aae4446638 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 651aff5205..6f55bdda15 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 3ff9546d35..6126625da6 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 f14c9730a0..607fa30792 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 3d6dffcb8c..5cfb5e7b58 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 54e041e694..7be86e2089 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 0000000000..230de9d816 --- /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 0000000000..baba303bf1 --- /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 0000000000..1e04ef49b6 --- /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 0000000000..1ce2830a95 --- /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 3d64511f64..4d104487fb 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 0000000000..d91986b659 --- /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/1789] 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 1122b4ab0f..eec3e3ba33 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 607fa30792..18190223b2 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 7be86e2089..e0b65f8bd1 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 0000000000..fc85379bc5 --- /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/1789] 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 eec3e3ba33..d50e2497e1 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 9160281651..b5b1f2f793 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 0000000000..2be7d51165 --- /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 0000000000..8e98e04565 --- /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 0000000000..23c21b1271 --- /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/1789] 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 c4bb1aec92..9269379387 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 0000000000..a9116442b1 --- /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 0000000000..2b648f77d7 --- /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 0000000000..17feae9b10 --- /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/1789] 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 366c3da0b6..0900f12909 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/1789] 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 8424ebff8c..0306a35e97 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/1789] 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 0900f12909..2a5b075d2d 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 f1e0771eb4..bca5fef36b 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/1789] 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 dc75eb024e..8240e35e95 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 036d09c974..0b5861c1c0 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 0000000000..eb0c53f755 --- /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/1789] 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 2f1317e920..a46edf26c4 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/1789] 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 11a651dc00..6190c0a39a 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 84464f30bd..8f6fd25819 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 40b1c85587..bc91f8595b 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/1789] 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 2a5b075d2d..341b7261c9 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/1789] 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 1d19ee7565..31c726f6de 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 cc9f6112fc..7f3d9638b2 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 0000000000..a46af9b530 --- /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/1789] 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 c667c6e5e3..05228b7016 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 8240e35e95..a376744bc2 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 0b5861c1c0..0564fbe509 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 0000000000..109b09b507 --- /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/1789] 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 f0e2f629a5..42a1a8ea38 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 328de3f172..3ca27690db 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 566c651fd4..3115a897b7 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 ce5d1615f4..f9bef9b5d1 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 0000000000..d82bc1e465 --- /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 a844a30065..b615d9deea 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/1789] 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 48caf8f4f1..b82eb1596f 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 0000000000..19ae6e4e6a --- /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/1789] 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 b7e91bd3d9..54f4ff3186 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 6f55bdda15..5215a30606 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 9fef24a587..fb935ac630 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 0ad78520f2..5a05575059 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 0000000000..3e0d793052 --- /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 0000000000..2bdd9611b4 --- /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/1789] 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 9a2fc917e7..a8a9554062 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 8eedc78214..64745b60d9 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 0000000000..4c26243016 --- /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/1789] 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 975365a341..6abb943d73 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 0000000000..44c79a0444 --- /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/1789] 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 abf9bff20c..69e8b03786 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 1d12b75157..b33ed3150a 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 84c475d48a..51854608d7 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 322c33547f..bc3823a68b 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 9ad5cceea7..fe111fb0e4 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 5480eb394c..c2f1edee23 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 814d93ae60..e84f4f0901 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 67f6f42b4e..8300080801 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 e9f82b1476..e22db7862e 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 6eacf1535d..adcf6472a6 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 058f6c1a1f..dfc01e41c8 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 a8ffcaf800..cde66da1c8 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 e2364e5aa5..def053f902 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 b82eb1596f..bedf530cdc 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 8de2c7a5d9..0c658ce936 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 248eeecc30..8df09d5f93 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 b1c453648a..e7030f8ad8 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 1b79507bad..57d68802f9 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 055029152e..d354c028ea 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 92e14eaba2..44bd919027 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 0564fbe509..8f7081b679 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 eb0c53f755..9ed7c9b217 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/1789] 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 8300080801..444aaeb25f 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 e22db7862e..b371026745 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/1789] 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 0c0af430e8..9fd81c9e90 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 0c2bbd532c..6ec2c329d4 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 db7e3f2a32..e4c34e609f 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 5a05575059..19764b31ff 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 bc3823a68b..2408a6acde 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 19f40e9421..2ead0c53c5 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 0000000000..1141e6c3dd --- /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 cde66da1c8..b61120eb27 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 bedf530cdc..95a55073ed 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 57d68802f9..f1e6d16f50 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 0000000000..3a02557bd5 --- /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 b615d9deea..7dd88c8e69 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/1789] 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 1426b804a1..2ce18d91be 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 de4f3bee49..8fee63615c 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 e505fb0511..34f65e7035 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 0306a35e97..5c54234da5 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 0000000000..3b705fbf07 --- /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/1789] 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 d34bbec06a..b2f810732c 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/1789] 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 6190c0a39a..38713aa8ce 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 4430e7fe45..2f57e11abd 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 2ead0c53c5..29b51998e6 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 1141e6c3dd..1ac47551ec 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/1789] Fix typo false-positive --- .typos.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.typos.toml b/.typos.toml index 2687f239ed..78c99b1d76 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/1789] 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 e2a6e57208..f83a55d4aa 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/1789] 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 6dbb056300..bef103a992 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 1ab1cfb464..bf524029dd 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 02dfd6869f..db8896c9cb 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/1789] 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 6ec2c329d4..eb59d4ec00 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/1789] 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 29b51998e6..99f52f6292 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 1ac47551ec..466ccc8e93 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/1789] 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 a4758648c6..018e1da4f8 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 38713aa8ce..d252aaef59 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 9fd81c9e90..fd0404dc46 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 31c726f6de..12672c9d53 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 bf89cbdf48..1134614b2f 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 eb59d4ec00..14a1d49643 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 3db627179e..431026f938 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 df06443436..c26ef1dcfe 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 db7c28afff..a176905563 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 7369715660..3913479a89 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 75f020fe77..f428436b48 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 b9b489c12a..045f66913b 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 218edf5734..415da4d6bc 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 58abdef6a6..d580b0c2b9 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 6eca4510d9..b9ffc20442 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 8fee63615c..4ad442ac42 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 19764b31ff..0859825701 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 6b43f932c4..35ba562a16 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 99f52f6292..2ef0c3d184 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 466ccc8e93..9faf5f715a 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 affb0fc679..8de4ae7bed 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 1ebd091d9d..c3f2887c98 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 6fed25e6c2..a74effd6e0 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 f4bae0e0ae..d537543139 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 a5c79a5ec8..5e28b2186e 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 6cf4b55072..6de7bc4073 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 07dc631426..f20482f72f 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 5c54234da5..ea87452e90 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 b374da4943..5e57ee2994 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/1789] 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 a376744bc2..91e5260488 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 8f7081b679..df34c15d50 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 109b09b507..d98eed672a 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/1789] 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 d252aaef59..294480459c 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 14a1d49643..4c4e4fd5d8 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 0859825701..57b1eb38e9 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/1789] 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 294480459c..d252aaef59 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 57b1eb38e9..0859825701 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/1789] 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 aae4446638..2dfc3cee7a 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 0564f2a957..b32efc480d 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 88b2b619c2..630036d8cd 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 0000000000..0d65083735 --- /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/1789] `@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 bd637913bc..aed8352077 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 4eb95d8449..0bf4133b79 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 0000000000..419705e89c --- /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/1789] 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 a3ea8273e6..f343ffe6ca 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/1789] 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 a587099844..538f81fc66 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/1789] 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 f343ffe6ca..bb598d8fb1 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 fd0404dc46..8369ac2430 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/1789] 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 b32efc480d..239e732056 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 630036d8cd..c83a84d425 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 0d65083735..1b5896c788 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/1789] 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 f182372fc0..855b015529 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 d12dda936e..d3809e95f5 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 0000000000..44246463fa --- /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/1789] 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 855b015529..e9d101faa7 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 d3809e95f5..73f784325f 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 5c417800f1..cd14b78cec 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/1789] 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 0000000000..a3e4addc62 --- /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 a3ffa071b7..7ce9a166c7 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 0000000000..33bd0ff1ab --- /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 0000000000..738766d8a7 --- /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/1789] 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 42a1a8ea38..bc51e1d102 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 34f65e7035..6c3f1fd567 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 0000000000..d39ae6bd94 --- /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/1789] 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 df4a120477..ab04c623f4 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/1789] 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 0e7b7c328d..4350b59252 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 b085b9a781..189cba0b5a 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 fcdca30b44..8da8b7343e 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 32a755653a..843f550880 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 0000000000..3f1b50318b --- /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 0000000000..8b8d4eec3e --- /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/1789] 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 0000000000..fb0d0a1f9e --- /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/1789] 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 7197015333..78c84ba3be 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 189cba0b5a..37e2f7244f 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 822aaa6e37..fc8ebd021a 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 4aa00a2064..f45e41b774 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/1789] 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 189cba0b5a..c02e342e1e 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/1789] 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 4350b59252..0e39ac8ab6 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/1789] 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 85fb77ed81..91ca0f68ef 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/1789] 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 74b47dea0b..38d1e6741b 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 2c22dfd35a..16d92ed767 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 0000000000..92221f9040 --- /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/1789] 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 4d9733a493..9db84af00d 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 0000000000..2f1ba9e50a --- /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/1789] 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 0000000000..97ce54a9af --- /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/1789] 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 2f57e11abd..3c1dc42d5f 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 79e015fbfe..518fabc0b9 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 0000000000..e27fcfa175 --- /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 0000000000..3aa2d4c21b --- /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 0000000000..4f52d30b24 --- /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/1789] 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 a052a43416..15d1d86706 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 db36561ab6..c6226f5a5f 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 8cf678ae56..1f1a0e168e 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 202e5b1700..705e237a85 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 51e121a4c1..5c96b64e67 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 12d3495098..19482b7fd0 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 cd831db4d8..11adfa5dcb 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 c5fd9fc1d8..598a358927 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/1789] 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 023cc16756..f1a70365a7 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 f1e6d16f50..e6aa299b75 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 0000000000..237f71e684 --- /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/1789] 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 518fabc0b9..55da1ddeb3 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 0000000000..f23eb8f516 --- /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/1789] 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 91ca0f68ef..45e956408f 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 0000000000..ee8450a8e3 --- /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/1789] 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 1a8bc8b4b7..fd6f669c7c 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 95bf5c3c29..1d3376b781 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 0000000000..7d9c064f4b --- /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 1a8bc8b4b7..fd6f669c7c 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 1a8bc8b4b7..fd6f669c7c 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/1789] 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 3c1dc42d5f..73a34c2175 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 078ea41b12..9d386b0c50 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 f4d3898790..17077d7bfc 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 54fb89c2c7..859718b615 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 ab04c623f4..bbd10c4ec7 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 0000000000..e3cb0d28f6 --- /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/1789] 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 4f1e558254..a639bf6c0e 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 f20482f72f..d9307d0bc7 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 0000000000..435359de5e --- /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/1789] 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 d3ef021189..b0edfcadf9 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 55da1ddeb3..78e8d86ccf 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 0000000000..82fac73327 --- /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/1789] 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 12672c9d53..ecf824a240 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 7f3d9638b2..9dd8161487 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 78e8d86ccf..6557f97ed3 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 0000000000..463362516a --- /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 0000000000..5d967399c1 --- /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/1789] 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 45e956408f..8116216499 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 9dd8161487..a2e9ef0619 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 6557f97ed3..a1e7fbd212 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 0000000000..83edeb9fd5 --- /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/1789] 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 e48e757bfe..45e25730e1 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 fc8ebd021a..b94df8dae2 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 434d25be5d..1189bc2232 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/1789] 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 5e57ee2994..1c077ad8ad 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 0000000000..995f2d8216 --- /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/1789] 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 a1e7fbd212..3323d3a0dd 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 0000000000..f59f2bdd6a --- /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/1789] 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 54f4ff3186..e764d064eb 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 f7328f7863..3791c293a1 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 7e997503a3..0db2ffc546 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 0000000000..2498f61efb --- /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 0000000000..6cf63b46a7 --- /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/1789] 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 73a34c2175..c016b2869e 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 859718b615..1deb2e8695 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 c51ea31efc..6654e46378 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/1789] 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 8116216499..1a204fd548 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/1789] 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 1a204fd548..a141b5dcdc 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 6cd33e10d5..67b2785fa9 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 ee8450a8e3..b89a699dd3 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 1c077ad8ad..533c53c25d 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 a3db0d1072..8d2adbdb98 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 f876b3d823..0745db66a6 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/1789] 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 a141b5dcdc..1bf6ec4d54 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/1789] 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 9db84af00d..eb7fe43290 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 0000000000..a4429d9d43 --- /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/1789] 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 1bf6ec4d54..0237084895 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/1789] 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 0237084895..b9dde3c62d 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/1789] 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 b9dde3c62d..ff4f3b9657 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/1789] 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 838d2ecd5c..f4c9cd19fc 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 97737499a5..80032561d9 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 0c5c33759a..69cbb72a62 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/1789] 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 d87581f69c..b29ca65bd4 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 cf547b909c..542ba55e5e 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 0000000000..1a7fe447d1 --- /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/1789] Fix build --- Makefile | 1 + build/collision-detector.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d50e2497e1..9e007ae2cc 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 03c717dfff..c3d69c08a7 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/1789] 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 c3d69c08a7..a687cd3ea4 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/1789] 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 a46396f12c..0a4174d4e0 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 9a1eca0188..ff1626d7d2 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/1789] 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 70f18bcbcc..4c475096ef 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 4e9673070f..eac07303a2 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 c7ec6ed0ad..0aecf4c09c 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 966f8e9e41..d54ae3a02f 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 0000000000..85ae54496e --- /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 0000000000..4138ce73c9 --- /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 55af82fe30..c390bbb6da 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 fe4af36466..e23655217c 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/1789] 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 4ab99fc5d3..31599aaee4 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 212fdcf422..4226e76fbd 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 969154c3c8..80724ea18a 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 d1727f5824..2aba60730f 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 f409f9c062..1708a1f53b 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 0db2ffc546..80ea03ac06 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 1ae0078880..f6057f8cf8 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 88589db6cc..81793c6ba0 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 6e44a11217..27fdb1ce80 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 8c0f52dc3b..acc4f24f2d 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 4c31b63050..96dcd36820 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/1789] 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 cd776efa5f..3f3a053368 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 a73ff8ec72..fe7bacfed4 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 0000000000..8066316400 --- /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/1789] 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 fb0d0a1f9e..a775284247 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/1789] 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 968b3d75f3..23c3aafdaa 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 19bc52fe80..68957aa2e5 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 e6b2c9dbb5..954c3a8669 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 fc665ed901..2c7163434c 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 32086476be..838201ffde 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 0000000000..10809e566a --- /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 acbbd53dd3..e544eeeb5b 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 51210125cf..2cfc7891b5 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 0000000000..3a917c0c81 --- /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 17aa83b245..e6b29e2809 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 0000000000..a1109c5858 --- /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/1789] 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 499ebb48e8..a142ecdca8 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 24ecfb9565..b2ae030782 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 6d099d1c10..dcb8fa8982 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 4560fde44c..68a41d3e9f 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/1789] 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 5e574eb6d2..d2b87d307a 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/1789] 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 d2b87d307a..e02de443a5 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/1789] 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 ecf824a240..e31c6c4c3c 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 533c53c25d..7c80c91cd6 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 0000000000..12fafa7469 --- /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 0000000000..b1b824d541 --- /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 0000000000..3ce06cc10d --- /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/1789] 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 c016b2869e..105204cee7 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 6654e46378..b5496d324d 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/1789] 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 d4dc6fe133..f76b230f2e 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 c0617856f7..8599b4bd6e 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 4ec5028172..584e644809 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 bcaa939b76..c45f048e95 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/1789] 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 c45f048e95..aca1242231 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/1789] 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 aca1242231..efa83c9191 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/1789] 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 cd91c0aa80..da6a44e735 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 51a1fc9d85..3cc16fa182 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 0000000000..d5f624f5df --- /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/1789] 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 73f330cb12..50a57278b0 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 3323d3a0dd..9a1f34bf73 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 0000000000..c0012503d0 --- /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/1789] 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 105204cee7..33d3c34a7e 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 0000000000..dfcb626150 --- /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/1789] 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 33d3c34a7e..485d8277cf 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/1789] 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 485d8277cf..52b3d76d45 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 0000000000..f83d7d8b5f --- /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 7c80c91cd6..2ba73fdc96 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 995f2d8216..0000000000 --- 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/1789] 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 d7c4673b40..348be4eb68 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/1789] 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 602152e12f..5e8232b803 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/1789] 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 5e8232b803..602152e12f 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/1789] 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 348be4eb68..d7c4673b40 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/1789] 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 8599b4bd6e..7beb64aed1 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 0000000000..8f0876667a --- /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 0000000000..6c42edbd19 --- /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 0000000000..87d3466236 --- /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 16d92ed767..f96185de69 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 0000000000..d1d3337111 --- /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/1789] 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 ff4f3b9657..e3bc5fd2e8 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 0c10a1c43b..6456765aae 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 0000000000..8ba79bff66 --- /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/1789] 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 e3bc5fd2e8..eba5f2e853 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 0000000000..ee7aadb686 --- /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 b94df8dae2..4797cc4dba 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 0000000000..3a01127243 --- /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 0000000000..30fb66f1a7 --- /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/1789] 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 1b1bf800f9..b285f56e12 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 0000000000..301d034b2d --- /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 eba5f2e853..32b7233344 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 62c18fce6d..0135b2dab7 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 06ed10355c..0f9e82cb21 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 2dfc3cee7a..a3eb0567ea 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 7fb27f8351..a2bb6889d5 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 4c4e4fd5d8..05a9f9b6a6 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 edcbbf7b7f..a3e8c5224f 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 38592e632e..407233ae0b 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 6456765aae..e7678b1449 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 3d28594e7e..6ee268c32a 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 0000000000..23e4039715 --- /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/1789] 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 9d23945857..40b1848c3a 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 4797cc4dba..4c72f04c61 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 0000000000..bcf15355af --- /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/1789] 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 da6a44e735..64c2f0c496 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 32e017a254..ff234b6092 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 69eb455eaf..0383ea4c5a 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 65e7fd1691..9169af89ba 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 0c190b23fc..19a9a1eb5c 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 a30f261fae..ff99e4699c 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 ebb4220372..2081bb0624 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 165b61b63e..13cf62ed26 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 72a268c506..33131edfe8 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 4d065367a2..d9335610d3 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 12020de0b9..709f298635 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 8861e9f036..88bbe9fad6 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 34b1b72756..4620565210 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 dfbcab477e..1b5dc597b7 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 f7230851e4..d5e650e708 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 9a1f34bf73..ca7b7372a9 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/1789] 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 64c2f0c496..a23e3445d8 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 ff99e4699c..96b810431d 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 2081bb0624..039a1895f5 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 88bbe9fad6..5e87970c8e 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 57c486638e..c6ba4824c2 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 ca7b7372a9..58806979a6 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 0000000000..4e1252e5b4 --- /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/1789] 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 80ea03ac06..b40e3bb83e 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/1789] 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 f83a55d4aa..c772596bcc 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 7ce9a166c7..3114bedfe9 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 0000000000..1322e06e4f --- /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/1789] 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 da7cbe82c2..38806ce167 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 a2cf8cd484..ede07b3c7b 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 0000000000..5473def6fe --- /dev/null +++ b/tests/notAutoloaded/nonexistentClasses.php @@ -0,0 +1,15 @@ + Date: Wed, 26 Mar 2025 16:19:12 +0100 Subject: [PATCH 1229/1789] 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 0000000000..25c7a11459 --- /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 0000000000..8593429e98 --- /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 45e25730e1..2f28598257 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 0000000000..5d1edc427b --- /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 5473def6fe..35d69e738d 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/1789] 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 52b3d76d45..1e58feac38 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 e7678b1449..d515fb2379 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 0000000000..189d88cc8b --- /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/1789] 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 fbc602329e..32208ad83e 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 0c1d4e1b49..838d7e15e9 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 fe7bacfed4..3c54d1da2c 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 0000000000..60e6ecf05c --- /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 9269379387..dfd42577ec 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 0000000000..3b705fbf07 --- /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/1789] 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 e75c590756..5390b57ac9 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 d61265e3e7..92012f0dbe 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 2649996d3f..54206ff2de 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 5a24e75502..1518e6bc28 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 746c3b9007..153f2b3173 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 06cb9fdf88..fcba96a92e 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 ad2f2f7cba..6ce92598f6 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/1789] 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 58806979a6..52305f8c6f 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 0000000000..b5ba2616cf --- /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/1789] 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 0000000000..a3ae370111 --- /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 2f28598257..cd0f52534e 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 38806ce167..dc3e884c88 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 5d1edc427b..706f0e1533 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 ede07b3c7b..c8220a0e9a 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 0000000000..61c09f8bbf --- /dev/null +++ b/tests/notAutoloaded/nonexistentClasses-error.php @@ -0,0 +1,21 @@ + Date: Fri, 28 Mar 2025 14:26:26 +0100 Subject: [PATCH 1235/1789] 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 f1a70365a7..467cf98a99 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 407233ae0b..8b4d51142a 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 e6aa299b75..a0aa4a72f5 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 0000000000..cb7e998027 --- /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/1789] 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 68957aa2e5..8c01d0118a 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 d20760f847..06cbf2e9ca 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 2d18943608..b91b0537bf 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 ba67e53322..751a69a41a 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 e544eeeb5b..133e6fd673 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 0000000000..63339ebe09 --- /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/1789] 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 ab9f86d034..1361f82728 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 52305f8c6f..df2cb2cf26 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 0000000000..c5d31f966c --- /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/1789] 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 1361f82728..149536a573 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 63b9e0037c..8101c1744b 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/1789] 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 5e803a6af9..e3ae5e23f5 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 37ae13258f..29b57c1593 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 8b89e0935e..167cad5f8e 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 0000000000..4a9a22e4c5 --- /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 7dd88c8e69..a62073a40c 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/1789] 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 0135b2dab7..668c3aa9bd 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 0000000000..437dc09ae3 --- /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 54206ff2de..12307bf5bc 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/1789] 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 59cbedb60c..9c456aec18 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 a3e82f2a34..1958ec232a 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 0000000000..cff0bc5897 --- /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/1789] 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 29a936b35a..f5022dc94c 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 826586689a..de2f2e9de3 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 0000000000..fd242cc639 --- /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/1789] Update Renovate --- .github/renovate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 5cb51461c6..59522a2dbc 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/1789] 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 8eeb63a4d5..2366222087 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 5aea839c83..8d0050cfd3 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/1789] 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 5e15482336..7723ee2151 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 469c8956bd..0878d2f302 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 29898f7532..20abbe4b24 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/1789] 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 3c2428f5db..aea642904a 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/1789] 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 0859825701..e5b2540d7b 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 f17c18ea97..9a1bdf2fea 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/1789] 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 b5b1f2f793..084738089e 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 3b5f68d64a..a1e3872d6b 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 9cd92e09e7..2deca2ac0d 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 e764d064eb..555e7ec53c 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 3791c293a1..69fc9380b9 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 0374aa268f..b0737cbcc9 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 d1863e1945..8735b22084 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 5e40800631..063a563912 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 69ecc87fb5..d13aefbd7a 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 e0ff7c27ba..581f45f3bc 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 aff2164a91..d6caa6a580 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 5fc694a46b..dc87b36cfa 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 4c0a424c1b..b8cf691aeb 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 28ca78679e..7351d2a2bb 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 5730ea3a9b..a5a420e24c 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 cff3184a6f..74df41612b 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 c3e9fda73f..f45c38cd67 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 d0e52ead39..2bae50132b 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 1c826e424c..f4af37da86 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 a97e2caddb..9805a445b8 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 b9ffc20442..63c1de6c2b 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 68d8a95465..457ffd29a9 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 b93db1ea45..3a82facc94 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 3815a963aa..375e246f35 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 14f6d43647..f6a602b2a4 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 076ae342a0..764c868e53 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 94e526da0c..80f1034120 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 62678e3f14..d2d1dc5095 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 416583546f..2f9dd97903 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 ff1be83109..278e2d805d 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 d37f09084b..3027ce23b7 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 8936631496..0991d178cc 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 df337d9bdf..757b25cbd7 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 43c6020744..c907f46ddd 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 df2cb2cf26..0198587347 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 45750ac2a4..e131c311a4 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 9049635db0..533dd185ae 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 38afbe6137..ff9ae587d7 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 ba43922b08..15abfb7cef 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 5804527769..f08a205a79 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 ee36958b19..e5d5f11c2a 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 4503a788e8..642b296e4f 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 b3a71307ec..fb0e7991fd 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 d4a9dc96d5..4f0e9ea4ce 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 64b9783877..99b5f29373 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 838201ffde..e1e48ef78e 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 fa3ca260c9..ca4132139f 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 0e6d0b066f..9841cd6ed4 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 49b083cd46..d6369b56a2 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 12183ebc85..a2533f17d9 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 f83d296867..e706e005d9 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 cadda3b992..4caaf6f8e5 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 5286e079fc..b4fb7e2399 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 7852d40555..bf5e7ab8af 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 47b81f1ea1..9f6ac7b3ec 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 76dc96f81c..9654660857 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 a84377316b..e99a521056 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 ae7ff38ec5..0c221078d2 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 fce38f5d98..5cff88d707 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 5317938620..bdacfb3c6a 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 e05283e71b..c389d025a0 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 9465723963..80d1a3e9e1 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 c94636b5e1..d7920d043e 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 4ec02315e1..826047dbb9 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 1266a5a553..4f822c2a64 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 f45a1b894d..f42aaca316 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 7b6d03ce19..d30fbcec67 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 16dbd9dfbf..4a0b85b34b 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 92012f0dbe..83a3a19578 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 36346ad255..e31c3d2faa 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 b9ac08e1bd..69f97f9d0e 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 1be1cfae69..b88e13b002 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 a3ba642464..a5f2fa7232 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 b61120eb27..26b4a4b906 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 1958ec232a..0eea3694d1 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 15734fdf18..4344c32b14 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 ccff7cb19d..7ee37a1458 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 41cc608457..758c1e00eb 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 6ffeadca25..938020a0ba 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 1961e8e81c..0a7f95b270 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 e13fc184fc..18bd5ed206 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 65e4714487..8f2d3415b6 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 678738ac50..c5d89a302a 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 e4708c5f4c..e652375414 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 0ac6304231..368ce6b286 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 12307bf5bc..b5ca981736 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 2fc8264821..6aee2ae8a3 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 5c84c216a6..cade4c576b 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 d658d3aeb4..500199d28e 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 133e6fd673..bee0436a8d 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 2cfc7891b5..8b1a40dd21 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 f9ca07781c..67bd722602 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 90564ff6d2..1fa0216870 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 c87aabde90..0af7093585 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 5058756f1c..8b558e4512 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 0c658ce936..856a263c2a 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 169acc6693..8cec36f236 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 06a4be03c5..c13cde0b4e 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 372a5b311a..f3e4ad2691 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 135194070b..cc27629dcf 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 c6879f2dc1..3305d725c4 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 63c757ecc8..63a9630c09 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 afd0611e89..5b1b47c41e 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 7b5f476bc9..d2477d1ca6 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 1518e6bc28..74e50e10f9 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 153f2b3173..d14f249dcc 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 6ce92598f6..4a795dd771 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 d1e43fd0e1..0640efd885 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 a0aa4a72f5..3fab4606d8 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 ea4b27f4f8..ab15703303 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 7060aeecea..88834051bb 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 d3398ed7e7..55df196d56 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 eae03f029c..ad1a9b43a9 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 97a9f1b1f6..cfe6972341 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 6f4a6121ec..e9dcd4a483 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 5f627c1902..fe5d59a5b5 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 0857934cb5..83c919af1f 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 2ba73fdc96..8d050e7636 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 21c70b7adb..b11e08f127 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 fb35b438a0..67e500fc63 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 95e94df168..f8268f8fcd 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 8f3b40c7b6..5929aad03a 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 f1c3af3cc0..d26101b421 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/1789] 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 32b7233344..2d23a40665 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 e00c33313d..e8bbb0d1fd 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 0000000000..66d362f527 --- /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/1789] 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 555e7ec53c..4d18b9552e 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 2d23a40665..42034f0ae3 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 668c3aa9bd..e5cffa8560 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 528f037f8b..303f0d2c0d 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 cd0f52534e..e1e3cdb200 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 dc3e884c88..f693c1fe36 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 bacd85a967..78bfdfa4d7 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 0000000000..8f03858767 --- /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 0000000000..7ab2ea364f --- /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 9841cd6ed4..4e026df3a6 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 0000000000..6c7b8cecbf --- /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 d30fbcec67..0da9819e08 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 0000000000..7be2b0bf7b --- /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 0000000000..1d8640a9ae --- /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 3b481b8f14..f389c3f628 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 0000000000..d537fb6527 --- /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 0000000000..0a030d7bd0 --- /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 f45e41b774..6e7a5f8181 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 f25e40b2d7..f92076b16a 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 0745db66a6..0e849ab84e 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 0000000000..2c48e6249d --- /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/1789] 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 bf524029dd..a219edf40e 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/1789] 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 a219edf40e..27f5c4cf65 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/1789] 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 80032561d9..d161d374e4 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 bef103a992..db3a405be2 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 27f5c4cf65..0f87ec80ec 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 69cbb72a62..6136b1c068 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/1789] 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 3ec87ab42e..44eaccbbd1 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 4d18b9552e..a9a72fdf01 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 e5cffa8560..6910dfa051 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 6e28b549d3..9610d50c9d 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 4029d26554..707aeca28a 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 05a9f9b6a6..6face3bab1 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 0000000000..39e09047ff --- /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 0000000000..9c526121e9 --- /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 0000000000..af51945d8e --- /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 a0d4d17471..fc0dd70dbe 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 e1e3cdb200..026069146a 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 f693c1fe36..0d7e0306d1 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 78bfdfa4d7..1b41db9157 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 0000000000..c52796550d --- /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 0000000000..95997bf046 --- /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 0000000000..dd0ac38c86 --- /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 0000000000..6b79cfe3f2 --- /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 0000000000..44710f9e9f --- /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 0000000000..9df05b1b41 --- /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/1789] 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 db3a405be2..42efad3eca 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 0f87ec80ec..bdeaa98f57 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/1789] 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 f2b72c235b..de4c904423 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 453667cd51..aae0e128e5 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/1789] 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 0383ea4c5a..2f90e08c22 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 5e87970c8e..545fd191f1 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 c6ba4824c2..7bd70492ee 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/1789] 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 0d6342e584..b5b0eb1df8 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 29b57c1593..e6cf82bf5f 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 ac9c8aa88f..64734d276f 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 090fcde892..9ec89f5c1d 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 55a1c372a5..48ab02dd92 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 e76b112f4d..568c6a8b7f 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 856a263c2a..1c62efa0c0 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 a62073a40c..a85b6a4709 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/1789] 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 69882bcfc5..4e7e2ee083 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 6face3bab1..f6e3da43e9 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 7058b8b510..b4d2fa5eb4 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/1789] 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 a9a72fdf01..6b64f0d1b1 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 69fc9380b9..abedcd1e62 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 b40e3bb83e..5335008c34 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/1789] 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 0eea3694d1..c5fce4e73a 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 0000000000..c4880d83f6 --- /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/1789] 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 303f0d2c0d..9669fccc8a 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 6e7a5f8181..178101b951 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 f92076b16a..fb3dbb66ee 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 0e849ab84e..ba73fbe2fb 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/1789] 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 f7d01f2da6..ff08ce4503 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 0000000000..4360d8bdd1 --- /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/1789] 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 6b64f0d1b1..7e11398a3e 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 abedcd1e62..a148f6fc92 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 5335008c34..e2ad34e817 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/1789] 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 df1bf3499a..a13e787666 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 ea34875cd1..726542e93d 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 0000000000..20ec2cac5f --- /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/1789] 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 e2864494e9..2e6d26a381 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 0000000000..a932a97491 --- /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/1789] 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 42034f0ae3..b15018fbc8 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/1789] 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 3ae92e09dc..3565a312ce 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 f942a62afa..a2f7f00980 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/1789] 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 3565a312ce..91f0e9544c 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/1789] `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 b15018fbc8..22a895e03c 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 0000000000..f3fce8fd9d --- /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 0000000000..7cbae1c8be --- /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 0000000000..a861ac8edd --- /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/1789] 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 7e11398a3e..faf1ce641c 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 9610d50c9d..18a2e766d3 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 0000000000..275314a998 --- /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 0000000000..fff3904d5e --- /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 0000000000..aac69f9a4c --- /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 0000000000..02b6e3e168 --- /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 0000000000..46c4bc8f35 --- /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/1789] 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 bff45debef..1dcd470825 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/1789] 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 aac69f9a4c..4fee1f8eef 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/1789] 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 6910dfa051..756ea8db31 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 0000000000..d294016ec5 --- /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 0000000000..cbdc816074 --- /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 0000000000..ed949a846f --- /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/1789] 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 b7aab264ff..696e0e5b08 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 9a309e3189..ade4e1c4aa 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 4c2efda773..c48d6904ce 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 a67f79e00b..dced9b6206 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 03a9a2b2ea..5cea392754 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 a9ec6063d8..34ec4e3e51 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 ae8292e32d..aafd7b658e 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 61ee5d767b..ecf72e435d 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 b2b45932a3..432fd69350 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 bd7ef46f2b..134e566eca 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 f0ce213ad7..eafd314157 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 3d8015ddaf..b330b6fdad 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 42bc13b430..5a9ea238cf 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 7aa841be15..2dcb9d3cc3 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 870d185d59..b26eda5849 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/1789] 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 76dd0d8904..22487e357c 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 2deca2ac0d..aaba4b9365 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 faf1ce641c..d3c6f958ec 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 a148f6fc92..d18df776e3 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 d252aaef59..c14025a4a4 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 432fd69350..c4915edddb 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 0000000000..98e7f9ea35 --- /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 0000000000..37d53e7af9 --- /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 0000000000..c33e3ab929 --- /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/1789] 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 275314a998..8de9bf5ffc 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/1789] 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 76fdf42a5d..a44135424c 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/1789] 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 bf4c3abc6b..85def351d2 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 fa1abc5b46..9c9a3d3abf 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/1789] 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 68224a0f9b..e68a6a61d3 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 847f535df1..79deb5576e 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 52d511c163..c82d2298f2 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 23a8d6194e..bc177e9d0b 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 0000000000..b82ecb820e --- /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/1789] 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 a44135424c..8e76f0d08f 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 79deb5576e..12caf4fdbf 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/1789] 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 9c9a3d3abf..918b56e624 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/1789] 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 c14025a4a4..24299aeaaa 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 c4915edddb..432fd69350 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 98e7f9ea35..fb54889d94 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 37d53e7af9..8605c44c9b 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/1789] 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 d3c6f958ec..db327fbfcc 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 24299aeaaa..19bd8da64d 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 fb54889d94..441d9f8f51 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 0000000000..b9f061bc3b --- /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 0000000000..f03478cfb5 --- /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 0000000000..a2e3ad5909 --- /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 02b6e3e168..70e14b0e03 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/1789] 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 42efad3eca..1b5ef4e515 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 bdeaa98f57..fbe52cba3b 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/1789] 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 22a895e03c..16a9c15f05 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 756ea8db31..b43f0298cc 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 d294016ec5..33f8a11e26 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 cbdc816074..2330c0c130 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 0198587347..941829a685 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 0000000000..d1dea20e05 --- /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 1f03a122bd..25e362f6cc 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 4c72f04c61..68cd2cc059 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 0000000000..c6a64cec05 --- /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 0000000000..288e19c21f --- /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 0000000000..c5d92bd1e0 --- /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 b954794c6e..5c308ea5ad 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 1c62efa0c0..bc3a1b1fae 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 0000000000..9f7ff6a28e --- /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 0000000000..0cc8b3ad0a --- /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 0000000000..a007fd2661 --- /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 0000000000..ea88940446 --- /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/1789] 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 7da56bdff1..dc0ce7e244 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/1789] 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 fbe52cba3b..5d0ec9adcd 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/1789] 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 5d0ec9adcd..e25e48c8d2 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/1789] 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 db327fbfcc..e4b43fe9ec 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 0000000000..66eb85f906 --- /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 0000000000..a6172e69dd --- /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 0000000000..c5de137c29 --- /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 0000000000..804042289a --- /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 0000000000..eefc896faa --- /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/1789] 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 1b5ef4e515..a184310b1b 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 e25e48c8d2..07344c7cd8 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/1789] 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 18a2e766d3..8a4fe377c0 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 df104b3a20..c391d69123 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 80d3af0f77..c2f8d5f00a 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 0000000000..6e3bf8cd17 --- /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 23c3aafdaa..5f2b864036 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 8735b22084..60a542ff17 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 063a563912..7a4d4d21eb 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 d13aefbd7a..328e238762 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 581f45f3bc..6101523b07 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 d6caa6a580..35d3f088fe 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 dc87b36cfa..97f541b5a9 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 b8cf691aeb..6c5ef87c20 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 7351d2a2bb..13f384974a 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 cfb270cadc..9621d3c9ec 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 2523e3a9b4..8c8eb5cfc1 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 a5a420e24c..d0fcaace7a 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 ddb3cf254d..20b0c004d1 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 1f6d6f1f7c..aecce7bdcf 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 74df41612b..2c6417eddd 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 8fb28e0888..de75fc13a6 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 33a3e80780..7ef205cbaf 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 f45c38cd67..e499f38ed8 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 c1f002c3b3..0ed1a2918a 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 f381cd0dfd..4ac620fbc2 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 f4af37da86..d873394c20 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 f855124285..e4cb31e36d 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 5df0e84af3..7f83eea193 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 dda84c8629..2dbd279da2 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 fcc4671708..6127bac985 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 63c1de6c2b..df72cfb6bf 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 457ffd29a9..6e246de285 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 3a82facc94..41407def84 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 1fecf4ffa3..4082427557 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 893192e250..cf91c5ece6 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 375e246f35..7dc718889e 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 6694ad3e42..47279e6e4e 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 f6a602b2a4..233082b3a9 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 9540555e4f..7b5779fa9f 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 de4be753d9..468e0a709f 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 764c868e53..616ce79827 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 80f1034120..7bd8156cad 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 d2d1dc5095..1fee991c46 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 be668710f6..dff1491617 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/1789] 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 c2f8d5f00a..c8d786976b 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 0000000000..1d1f619c99 --- /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 ca4132139f..83d23411de 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 4e026df3a6..26cf177300 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 d6369b56a2..f6e5ac6b5e 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 a2533f17d9..d51a34a621 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 e706e005d9..7ed5797895 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 4caaf6f8e5..c6a3db9ad4 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 b4fb7e2399..93e202ef4e 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 bf5e7ab8af..eb547315cf 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 9f6ac7b3ec..12631f5c74 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 9654660857..4662acc732 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 e99a521056..0748cdb4b6 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 0c221078d2..93e05c3675 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 5cff88d707..1ec2063551 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 bdacfb3c6a..1696a2515d 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 c389d025a0..d7c804c237 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 80d1a3e9e1..7ae81a0f18 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 d7920d043e..d11675e208 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 826047dbb9..04f0ecd7f3 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 4f822c2a64..def6012d0d 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 f42aaca316..9231ebd4e4 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 83a3a19578..5de7d45b5d 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 e31c3d2faa..1ec6854a48 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 b88e13b002..a46163b89c 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 758c1e00eb..360ed89a3c 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 5b603724ec..158d763831 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 db80402633..988b42b6a9 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 46e872ec74..f03764a657 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 0a7f95b270..2fc38ca188 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 c5d89a302a..7f8e3cef19 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 0538311ee5..0ef409ddf8 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 f9d15da674..af46e7e0f5 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 b997498703..3823a214f7 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 1db002fd94..758c11548d 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 773f6c30c3..470d48adc5 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 9276fec7c1..8450ba5f3e 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 84351d9ea9..335e1f707c 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 8b1a40dd21..98d4eaf364 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 e42210014d..170920bbf6 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 0af7093585..b2a85a50c3 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 8cec36f236..8f3161de63 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 c13cde0b4e..c49f6209f1 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 f3e4ad2691..13fa5a254b 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 8a69195287..2bc6ddba35 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 e2c76ab516..b5ed921568 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 b0d4d718ad..e10eff3ff1 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 d8ec3604b3..b0ed51d449 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 d2477d1ca6..ec26814c8f 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 c038a01891..99005c23bf 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 74e50e10f9..7893423227 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 d14f249dcc..93e516021a 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 4a795dd771..4104983d3f 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 ab15703303..355cf0dfa2 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 88834051bb..bae249fd95 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 cfe6972341..873654f585 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 cab45fe36a..ddb533c25f 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 e9dcd4a483..00874ab151 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 fe5d59a5b5..7a0e80e653 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 67e500fc63..b4e2455cb8 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/1789] 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 e4b43fe9ec..5baa7ed1e3 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 441d9f8f51..fe611165d0 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 0000000000..1767c02fbb --- /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/1789] 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 084738089e..ea85702a31 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 5baa7ed1e3..7e5de29f45 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 19bd8da64d..1bb9187861 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 c8d786976b..7ce8b7a27e 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 6e3bf8cd17..d3bf060ce9 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 0000000000..04f8a10339 --- /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 6e246de285..b167becd52 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 41407def84..daf1ee2ce1 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 0000000000..590bafd495 --- /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 0000000000..bdb30155c8 --- /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/1789] 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 d3bf060ce9..b2b2388791 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/1789] 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 b2b2388791..dd593c6d2d 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 04f8a10339..e72cf80756 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 df72cfb6bf..373e41ddd9 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 f03478cfb5..0f73bf967a 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 0000000000..fce54639f7 --- /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/1789] `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 ea0d7e2418..8f4f322d08 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 5d25367e70..5c19c29ae7 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 f6e3da43e9..a4adc7b9b1 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 0421bb3ff9..3bf6a6eb84 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 133629a45c..ca828a53fe 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 25f79396a1..1027c193f1 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 ca92d9258c..912b779e67 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 05228b7016..8307f36d7b 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 45438eb3c2..29975a5e3f 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 0b478d51c5..564613e219 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 c26ef1dcfe..0cd79eb232 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 797b1ede60..df7a33f84d 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 d0729a2260..71de28e1e6 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 eb2d00aed5..77e1ed0397 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 9f4fb2d904..04eceb4848 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 e3ae5e23f5..1093e24cb4 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 487a474827..10c2b7cccd 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 971a96b2f6..594c3278ca 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 1a1beed6c0..1e35513f0b 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 35ba562a16..c670914835 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 ff50c721d3..4625da358b 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 45fc20121f..fe2a3f6ee6 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/1789] 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 dd593c6d2d..e9a14b1861 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 5f2b864036..2d08602985 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 60a542ff17..9bd5e0ef5e 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 6101523b07..b1e639af29 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 35d3f088fe..1a7f8b2883 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 97f541b5a9..8d0d071471 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 13f384974a..c62d8abed5 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 d0fcaace7a..a5bc6a5b35 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 e499f38ed8..52ad3e72e2 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 e4cb31e36d..dfb4726019 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 2dbd279da2..27be48dd50 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 e72cf80756..d8b3a32e9a 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 4082427557..473036edb1 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 7bd8156cad..b15808ad5a 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 1fee991c46..869074e358 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/1789] 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 e9a14b1861..9981155bb2 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/1789] 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 9981155bb2..1c98edbf67 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 328e238762..08524a3fd4 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/1789] 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 590bafd495..0b847b8ca6 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 bdb30155c8..b892739f19 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/1789] 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 db046bbe10..01bb1dd29e 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 bc3a1b1fae..548589722e 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 0000000000..2b6e038fe9 --- /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/1789] 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 1bb9187861..3e8f127844 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 1c98edbf67..8fe44f58db 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 dfb4726019..1cf7482738 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/1789] 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 1cf7482738..af468ab670 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/1789] 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 8fe44f58db..d8727ac5f2 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/1789] 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 ea85702a31..11706d7659 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 7e5de29f45..80ca10a5db 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 8a4fe377c0..837b138a75 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 0000000000..1ab605cdd9 --- /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 0000000000..ba15cb4f9c --- /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 0000000000..f43c357190 --- /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 0000000000..aa8d49eae2 --- /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 0000000000..092b49e898 --- /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 0000000000..7bab882e70 --- /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 0000000000..555b5b0a52 --- /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 0000000000..d1961bd8e9 --- /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 0000000000..5026200f05 --- /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/1789] 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 0b68559b42..da37080cfa 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 9c456aec18..b787a4201b 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 e8bbb0d1fd..156b26cffa 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 0000000000..36109899a8 --- /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/1789] 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 1d4154261d..9835fb1591 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/1789] 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 aaba4b9365..51214ea554 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 80ca10a5db..18cba84c58 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 3e8f127844..dc8fcfbcc1 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 837b138a75..645d895293 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 0000000000..e6bbf4f3b9 --- /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 0000000000..2216c7435b --- /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 0000000000..260672d0d5 --- /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 0000000000..d3ee69910e --- /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 0000000000..6b56240a7f --- /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 0000000000..bf3b0921a1 --- /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 0000000000..182ef04e93 --- /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 0000000000..e1b057bf6b --- /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 0000000000..52cfb126b6 --- /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 0000000000..8f8e41e1ad --- /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 16f153456c..d03dfccb59 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/1789] 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 11706d7659..24b19d99bf 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 18cba84c58..7a4a43a4de 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 645d895293..ccffd14290 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 0000000000..89f58e22eb --- /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 0000000000..ebb5d989d1 --- /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 0000000000..8580714385 --- /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 0000000000..d20ac7eb18 --- /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 0000000000..033b140dab --- /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 0000000000..dd6f1c8402 --- /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/1789] 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 89f58e22eb..6971c7b82e 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 8580714385..e1ecf67c3d 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/1789] 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 fff3904d5e..d632582981 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/1789] 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 b43f0298cc..a769db8499 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/1789] 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 d4726fd5c2..2fbea580ca 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 e68a6a61d3..e6c0097db7 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 8e76f0d08f..a24df69710 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 149536a573..89c00f26d2 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 12caf4fdbf..caf08c8d65 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 918b56e624..f90994ee89 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/1789] 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 333d9de4a7..5ae08aac5d 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/1789] 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 a184310b1b..3a585da23c 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 07344c7cd8..91c8b4031c 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/1789] 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 432fd69350..b141bcec7e 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 5ae08aac5d..df89b763b1 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/1789] 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 3befd2d478..90e7c1f64d 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 0878d2f302..a2b8396dd5 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 cef87c8ac4..5e7e05f229 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/1789] 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 2d08602985..a14428d890 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 8c01d0118a..984e5c8efe 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 954c3a8669..04ffc64325 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 2c7163434c..2056a89dbe 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 156b26cffa..c94a5cff18 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 0000000000..eeafccb0de --- /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/1789] 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 a769db8499..d979a0f24a 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 9af225a3c1..8bcc663327 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 5ea351a924..1e3b55b0ee 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 961e2cbd95..f9fce63d94 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 90e7c1f64d..6600512da1 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 447bf76ecc..72f81cabdb 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 18ee7399bf..3fee19deb3 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 679c0b9824..a703decac4 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 72784cf114..568c847711 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 ea1c4b09ef..282b005c15 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 52f29d37a2..6b482c62e6 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 253af75e4e..e38e5be35a 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 e974888bc7..fcb6fcd893 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 e5b2540d7b..cc834e2950 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 c0114d8462..0f1778aa21 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 fe2a3f6ee6..c600f2d74a 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 0000000000..b73906fdfd --- /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 9445d8632b..4edd2300c1 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 0000000000..ae1946cdb2 --- /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 0000000000..7ec8f3012b --- /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 0000000000..9618bc818f --- /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 68cd2cc059..4c27bfd80f 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 0000000000..895b0573bf --- /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 bc177e9d0b..decb237d34 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 0000000000..ad00e11c1b --- /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/1789] 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 91c8b4031c..f07987c2b6 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/1789] 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 f07987c2b6..b304bec2cb 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/1789] 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 d2a5c575fc..df03926915 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/1789] 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 fa22bc9c06..6860694293 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 c94a5cff18..325f09f20b 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 0000000000..612927a31a --- /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/1789] 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 2408a6acde..85c4d02b47 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/1789] 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 9033aa3865..29b0801ece 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 c4000b9aff..55789676aa 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 2d52ee99e1..71800c5366 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 999c7169a3..4ad6a7cec4 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 16529f3a74..3236b7feee 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 0000000000..f0f21220d1 --- /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 0000000000..b89b47ba6d --- /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 0000000000..7dd533ff98 --- /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/1789] 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 13dc4a4f56..219e0450c1 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/1789] 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 8f9f5616f3..55e4843f36 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 8124ffe56a..90822edddd 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 0000000000..5dc721664e --- /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 0000000000..257fc17c85 --- /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 0000000000..8a98630a88 --- /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 0000000000..9f8ba1bfa3 --- /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 0000000000..4d81086d24 --- /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 0000000000..7827734e59 --- /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 0000000000..f4767621c2 --- /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 0000000000..dbe5307a82 --- /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 0000000000..e410f3827e --- /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 0000000000..f079aec189 --- /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 0000000000..1fc7bbab6c --- /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/1789] 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 dc0ce7e244..f69eca834e 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 40db6547a8..1ada3c4624 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/1789] 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 5beabcbcf3..9faa3563c7 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 0000000000..79c8160461 --- /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/1789] 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 dc8fcfbcc1..035be525fe 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 7ad74631b9..282c2189d2 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 46b5e712e1..7e54a2cb15 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 036cf3aa85..88c0b88ccd 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 09961335e7..b7d3fe977d 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 48eea97d6f..5cba66c18e 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 233082b3a9..e34e2c36cc 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 616ce79827..f8eed38f4f 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 2f9dd97903..20b9597722 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 1c40c4a9ce..8737855175 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 628041a032..68c1549091 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 c6324f7678..8555675bd3 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 c7fe99bc18..b811039ec7 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 e61c7c3a4d..93f18f28a9 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 87b5a12e5f..6d775a2f7c 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 02176773ac..06dbbeb0f5 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 0ca40d9fa4..03f3361bef 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 7893423227..68e2bfd718 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 93e516021a..96f423723c 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 eb362e5b61..6b5af9e340 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/1789] 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 b141bcec7e..432fd69350 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 0000000000..31e1b97281 --- /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/1789] 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 e2ad34e817..912ccdc858 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/1789] 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 7a4a43a4de..a0083d7753 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 51d8999c2f..70f5892c2b 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 0000000000..a0796febf4 --- /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/1789] 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 912ccdc858..e2ad34e817 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/1789] 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 d979a0f24a..58aa455c4a 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 761aa267ae..405b4a9adc 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 a2e9ef0619..2bfafc8404 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 0000000000..ee24d3580a --- /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 78d2899b8c..8018fd7f65 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/1789] 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 79c83c7b0f..70803e4379 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 1b770d6889..16e999bcb3 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 519649a384..f683ad9cbd 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 98eb5841f0..c6516dfe70 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 0000000000..e876597853 --- /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 8ffb1ddb89..a926f11293 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 aa4d8cd83b..f079710e49 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 0000000000..de601158ba --- /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 a798142941..548b240e97 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 0000000000..7b8de0491c --- /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 f96185de69..60795c0581 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/1789] 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 817b6a3273..ccc85a1c24 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 c310f6177c..9be4df6153 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 19d1eed263..a483c6d580 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 0000000000..b93de83178 --- /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/1789] 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 c772596bcc..f5d9a82715 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 dd46ed4c2a..a09f0a823a 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 a6712e6f69..b0707c61aa 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 0000000000..f5f524d44f --- /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/1789] 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 3a585da23c..1b5ef4e515 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 b304bec2cb..280e1ddce5 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/1789] 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 ccc85a1c24..a799cd4351 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 a483c6d580..c31874629d 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 0000000000..7a66941da8 --- /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 71045fd3ed..baa1f755cf 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 efa83c9191..ba2ce7e3be 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/1789] 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 1b5ef4e515..d10f417491 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 280e1ddce5..38509d2839 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/1789] 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 d9980551b2..47ac0acf51 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 dda84c8629..16ac12aa9d 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 0000000000..5308ad3b58 --- /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 8f56cc6cbb..dd4764ee66 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 3c54d1da2c..087f33b395 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 0000000000..990e8741bc --- /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/1789] 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 38509d2839..b893805aba 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/1789] 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 117779e376..dc25ccf544 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 f683ad9cbd..52abfdff30 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 087f33b395..1e5491bbc3 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 0000000000..5cd103b227 --- /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/1789] 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 990e8741bc..a2efd62ee5 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/1789] 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 205827901e..5e643bc081 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 cb8c2d6543..eaf112129d 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 0000000000..de44fa47f8 --- /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/1789] 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 a887358f52..a07ea29556 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 2e6d26a381..2242c477a5 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 16fe1d24df..58af77cd3b 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/1789] 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 6132011940..1938de18e3 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/1789] 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 5e643bc081..aab1b08dfc 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 0000000000..4d02ec8a8c --- /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 0000000000..2029c56df8 --- /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/1789] 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 58aa455c4a..d979a0f24a 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 405b4a9adc..761aa267ae 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 2bfafc8404..a2e9ef0619 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 ee24d3580a..0000000000 --- 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 8018fd7f65..78d2899b8c 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/1789] 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 3f887671ff..fce21cbbdc 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 a6172e69dd..b4c86efb47 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 b9f061bc3b..024ab87016 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 260672d0d5..9b0c011e83 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 0000000000..b7a102db9f --- /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 0000000000..a2575177a5 --- /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 0000000000..69eb2dbf4f --- /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 e1ecf67c3d..209c7304e1 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 0000000000..ab8ccf7708 --- /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 fdefc81398..9811979744 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 e1b057bf6b..267f5dd531 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/1789] 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 31ec1642c2..83340aa225 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/1789] 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 6c5ef87c20..3b9a850086 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 93e202ef4e..94e1ff695d 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 eb547315cf..1a84e5e170 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 0000000000..ba1dfd596e --- /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 64fb5c97ca..0289ff38af 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/1789] 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 1dde61b8b9..26703d01ef 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 0000000000..938982fe56 --- /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 0000000000..6db182b4f5 --- /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 4fee1f8eef..fbbd1c63fe 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 0000000000..6742e3116f --- /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/1789] 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 8f4f322d08..a79ffaaf3b 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 3bf6a6eb84..b43cf53453 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 ca828a53fe..c90f546344 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 1027c193f1..ad8b5a26d8 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 912b779e67..891491a1c5 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 fc0dd70dbe..19144986c4 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 8307f36d7b..8c4ea07439 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 29975a5e3f..6fb9316fb5 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 564613e219..924b249e6f 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 df7a33f84d..8b54c0785a 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 71de28e1e6..1e3e4d3179 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 77e1ed0397..1862d3059f 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 04eceb4848..ffb2a34363 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 1b14b785aa..b1a890b6be 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 c1806565e2..8f4e23a861 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 69eb2dbf4f..d6b0424be9 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 594c3278ca..1d022cb726 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 c5f5cd929c..dd9749e079 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 02d7467e57..b41b531c98 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 df34c15d50..cfe63c0dd7 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 d98eed672a..97ba5781cd 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/1789] 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 8fe817e91d..815534f310 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/1789] 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 7e54a2cb15..b8c0c2497a 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 6d775a2f7c..a01e02424d 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 0000000000..dd5180869c --- /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 0000000000..22727c110a --- /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 06dbbeb0f5..7a73af9da9 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/1789] 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 1938de18e3..f2a4c6d582 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 d51b5314b0..b54b6676d0 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 7399d39c97..826016fc92 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 7122c16150..210a467372 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/1789] 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 f2a4c6d582..2f8761856c 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 0000000000..fe3cfd7b59 --- /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/1789] 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 70f5892c2b..6827257179 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 a0796febf4..ef72623879 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 7f0a545edc..e55e7a38ba 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 ee4ad00527..4f830b96d9 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 0000000000..5fbc508799 --- /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 409535685f..bf8aba48a1 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/1789] 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 31f975460a..5a526b771e 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/1789] 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 b285f56e12..bf0a4652ac 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 4755296c6b..a6e805d69a 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 5451987023..0000000000 --- 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 1276d663ff..0cabab122c 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 5d633de380..41981f07d8 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 a313b60e2a..9607608462 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/1789] 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 16a9c15f05..856bb90170 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/1789] 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 d979a0f24a..8c070bb040 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 34bdbcfd31..1be9a3d301 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 695f59bf2a..3ebcbce620 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/1789] 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 5b5cb6b4e6..92675f4f19 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/1789] 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 b58cda6e15..9dc613ccb7 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/1789] 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 7dbd382405..59c9cf1bec 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/1789] 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 c92c6c5b74..b47b4930cb 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 34ec4e3e51..76be89b493 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 432fd69350..f032585242 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 d69e030e2d..3f4d3be629 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 c40188af2d..931fe0bc4c 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/1789] 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 22487e357c..81ea6c216b 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 24b19d99bf..6da8d4f26e 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 26703d01ef..5c29c4836a 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 d18df776e3..610d6cb2e2 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 0000000000..0b2842648b --- /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 0000000000..2212bafe42 --- /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 0000000000..bbcfea8ef8 --- /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/1789] 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 bf0a4652ac..d4a1986e42 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 d10f417491..4e6a2ad0b2 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 b893805aba..f4ddb42e9a 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/1789] 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 e8782a5f59..4008ec0b2a 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/1789] 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 0cd90db645..a6d189a7ba 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/1789] 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 6fd0eaf8ef..3f8b9f0d3c 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 8c7e405dc0..f76ae9dde4 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/1789] 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 82e6b89e0c..b28b9f9f5d 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 2ae0d7c9ff..0000000000 --- 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/1789] 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 f03764a657..a1a6beb6d0 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/1789] 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 29170f7fe9..98e9a52d41 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/1789] 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 d4a1986e42..51842a2530 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/1789] 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 8b54c0785a..7646de8651 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/1789] 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 1a9e64d7fe..f6c62244c0 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 5a526b771e..afc50f087d 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/1789] 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 8036cd78ee..434770b42d 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 5b6aaacc3b..b240a79322 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/1789] 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 52c13a9680..a35451b64f 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/1789] 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 046727de88..d9a3d69783 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/1789] 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 51842a2530..3d099857e2 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/1789] 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 4e6a2ad0b2..11a1ee76b7 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 f4ddb42e9a..59365ba683 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/1789] 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 2f8761856c..b716fae199 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/1789] 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 f409f9c062..2f33cf2433 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 567b61f798..099cb19e3a 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/1789] 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 4b90be1ec3..f1968b6a59 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 aab1b08dfc..c4aecd1561 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 0000000000..c23bf6bf67 --- /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/1789] 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 0e1117a66c..212d659037 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/1789] 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 c4aecd1561..ec557e3bbf 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 40668741d9..6a5b2cc317 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 6fd0eaf8ef..d7b2d5c403 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/1789] 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 83fae0a891..0eb55ab07d 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 8632d6b59c..280954f1d7 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 13505dbce7..48cae905ad 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/1789] 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 f1968b6a59..4bbfc2f346 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 ec557e3bbf..099a7e214b 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 d3359c6867..caca4e17bd 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 0000000000..f6908fca5f --- /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 0000000000..a91890871f --- /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 269f745015..333bc6136e 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 88589db6cc..ce858a3754 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 a112f22235..b11dbf8d53 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 5b88529382..b0e6a3c467 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 b0219c9fac..5158174918 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 b3085db0c6..d473ca3171 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 70a7624453..f4364f7ab2 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 fc6056eccb..572cb6635c 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 6e44a11217..487e9cf23e 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 5c8dad2f23..670bd89f43 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 a32bc2eb6a..a00a4e0724 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 4c31b63050..569f038aea 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 a7cb8997d8..2a0189e964 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/1789] 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 5158174918..088fce6353 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/1789] 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 b11dbf8d53..81dedfb56e 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/1789] 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 11a1ee76b7..cf8e9e4c5b 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 59365ba683..d5c883c79c 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/1789] 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 af58d51738..41a4e4fb9f 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 0000000000..9c3337fd40 --- /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 441c785c99..7856bf2d11 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/1789] 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 77a4f957fe..eaded15394 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/1789] 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 41a4e4fb9f..6f67e03520 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 7856bf2d11..8e963009e5 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/1789] 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 6f67e03520..e39adb451d 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 8e963009e5..0c4e2eb65d 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 1b41db9157..9877e01b77 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/1789] 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 4bbfc2f346..9a351d6332 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 0000000000..1796e7715c --- /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 0000000000..74db3f8f06 --- /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 0000000000..3357cbbc3c --- /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 0000000000..e8063edef4 --- /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/1789] 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 4ab99fc5d3..64215a4860 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 212fdcf422..eafa1a8ade 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 b88b3e0d31..aea76cc2b9 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 8d8cf59016..a76c648e6b 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 d1727f5824..87b957e35c 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 2f33cf2433..f0b89456bb 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 8fb9e53476..35831f2dca 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 ce858a3754..009bd17d97 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 b0e6a3c467..5b97a115ea 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 f4364f7ab2..54e1da4c41 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 487e9cf23e..d6cd900c3c 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 da033d28ba..464c421f34 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 f0f47ae021..1e46b05e6a 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 8e6eb17f61..8a2b062e71 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 569f038aea..7d5819e9c7 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/1789] 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 a55fd7831d..6f08799d4a 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/1789] Optimization --- .../InitializerExprTypeResolver.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 6f08799d4a..83b3e4ac96 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/1789] 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 af83130ac1..84bc69b35b 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/1789] 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 9a351d6332..eb5856de18 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 009bd17d97..b96bc347df 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/1789] 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 81dedfb56e..7452846e54 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/1789] 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 f5d9a82715..60f67ce5e2 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/1789] 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 96d30d0958..cdc66a14da 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 1e5491bbc3..8f853887b3 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 0000000000..9b9c4eabae --- /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/1789] 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 8f853887b3..6d487c2dda 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 c9244c6cec..71723a5afe 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/1789] 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 2adcf1b6ae..e98bdb9626 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/1789] 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 02fed04bcc..91aaf7037c 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 ed45d60e48..771490e7c9 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 0d93daeac8..b1ae823ea0 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 d031cb8816..23f1432c3c 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 a66be60a2d..91a74e428b 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 0000000000..7cce5e8b88 --- /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/1789] 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 9a12b00fbb..b0a39b8ccf 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 2e082297d1..5750c03c3e 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 0000000000..76386890bc --- /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/1789] 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 c6a9dfe91a..ec070388a3 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 0000000000..a100e47686 --- /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 0000000000..c5036fa153 --- /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 31538252d8..9358802c16 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 0000000000..f63acf9989 --- /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/1789] 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 a6c86b46cf..4b2208f5fe 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 2f436f5cd9..96115b942d 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 3108f1ef95..6af9e2737e 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 c981eab875..aa09192666 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 7142c9c4e6..38b6d24f54 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 0000000000..70b69adf79 --- /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 a6d189a7ba..1efc99470e 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/1789] 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 572cb6635c..13ff361922 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/1789] 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 9f0b7ee6c1..90bce8aff5 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 cf8e9e4c5b..0cdcf97071 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 d5c883c79c..0324294e6b 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 96115b942d..ecc7f612b7 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 6186f559f4..e47528377f 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 0000000000..e79126a15b --- /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 0000000000..0a4cad4e80 --- /dev/null +++ b/src/DependencyInjection/AutowiredService.php @@ -0,0 +1,11 @@ + Date: Sun, 25 May 2025 02:10:16 +0200 Subject: [PATCH 1418/1789] 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 d93e59843f..252d705fdf 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 8d0050cfd3..1590a29d66 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 602152e12f..14a7f79e42 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 d7c4673b40..510125a9ba 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 4da1b08076..337bd91212 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 d970a7b6f7..677851641a 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 9dbc3e3192..15654b55f6 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 0cdcf97071..6fa4ee2333 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 0324294e6b..3384613ba7 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/1789] 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 6fa4ee2333..83086ff40f 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 3384613ba7..03c1501ad4 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/1789] 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 ecc7f612b7..82cb3c8a4c 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 32505ef568..7478addf8c 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 b240a79322..b72e7fe36b 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 aaace19910..5701f75403 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/1789] 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 82cb3c8a4c..133e6b8de2 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 e79126a15b..05c91a4108 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 5ce81dbe84..7de2eebbd7 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/1789] 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 9536925b9d..a5107334fb 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/1789] 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 133e6b8de2..cfbe744457 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 05c91a4108..cdc63fc012 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 7f6a43930a..16a06b4a38 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 be867366b0..1180469b0d 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 f7d2a6dcb4..5a628a95f5 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 769f37bd1c..888b00a445 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 fce21cbbdc..8f8ad16229 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 ba15cb4f9c..b6840c712f 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 17126e4814..969963ed2b 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 66eb85f906..c9f1861b7c 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 1dcd470825..7c1b4fe37c 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 26ebbd2b46..75163a38a7 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 b4c86efb47..22230ca299 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 024ab87016..898075b514 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 9b0c011e83..5d45cb56a6 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 938982fe56..006282c94d 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/1789] 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 0a4cad4e80..8544459f9c 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/1789] 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 cfbe744457..7fe441dfc6 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 6e3c75b9a1..b039b62d00 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 5367744502..d722c87283 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 84ca5046de..1028ae2fa1 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 6827257179..4fec6cfe3e 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 ef72623879..22e7eb69d0 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 f809967ad8..663859a069 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 9871a469c9..2514aad8f8 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 fbadbac593..63cf6d9cf4 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 b8c7fdfe97..bf05500cba 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 62dc52abf0..6979c23759 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 30bbe16741..0a5f1f913e 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 220d8fa0ef..3cf1f7c9b7 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 97c514f427..58faf9d8bf 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 b5b0eb1df8..3e82909d22 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 e7436d8275..c6212c02cc 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 fe418f4daa..2844706529 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 bd9fdd6f0a..64fea966d6 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 a13714293c..c3865ada3c 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 74a2903ea5..550e668893 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 145756b971..591d9c8bde 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 712bd4edc3..1d4f7a1122 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 0f33b7f532..a0f49b4ca3 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 4e49cd465f..eef5642028 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 540dda82bb..61bfdf69e6 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 1d390b59d0..f61d48033e 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 970e2cb39f..7f955e6baa 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 e68f0338f7..68ab63375b 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 825f7d6c1a..0c90c7bfaa 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 762e577211..e1662dbe5f 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 cb6c35bbdd..b961e624e0 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 75f6a14b63..5ac2ba4606 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 85def351d2..5dd3c94174 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 5185fbccc1..061f3d4dff 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 2378a291b3..794b8df7ba 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 6e38a43a25..bf91435e62 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 2651cb6b90..e34e4d3859 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 df1b7022ab..3d1010aff0 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 83cfb69ef5..f332c99fc9 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 e798af6bbd..70d08ba9e0 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 9368cb4279..b87d34c466 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 b5de9de5f1..89970fad0b 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 1a404526f3..666a376cde 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 32113eb312..c74867e186 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 9e3e5c892d..c2782272cf 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 ef42505ede..eedc6a2fee 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 5de459fb73..e6ae81a8d4 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 d43c328df5..65cf25e8d5 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 cebc157759..44e3fb55b9 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 1a04c7d5b9..d1679bcdda 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 9f9a51cad3..d92e4e5882 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 26c2773555..1aeacdb0cf 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 612c66d786..e290bea849 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 bd1f73b5c2..14a06d90c0 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 6860694293..6223240bb1 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 35999b424b..e401fd3f31 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 b03a68655d..e4db21dda9 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 8b6a6133cf..757ffdf238 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 875529ae47..79c1c42124 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 85ba080957..7798d8f0e8 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 12236c20cf..71f8e6643f 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 fdbf783378..80a49d7cfc 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 15d1d86706..699b21c09c 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 15dc36a481..21aab0b82f 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 618fdb1fdc..b4ed3b2d18 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 65d82ccd7b..f9e551cc8a 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 a09f0a823a..4c18de0176 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 dc27fe349b..596e2ed8c1 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 8f6fd25819..48d6b5e712 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 a782db6686..7b14c0cce7 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 f0cc1561fb..63ab8b2669 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 9faa3563c7..26864a3054 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 893627aae2..913f97a10c 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 1a564b358c..8da8eda986 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 19d6c20b26..66035119d3 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 60fac48358..49cb0dffdb 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 6be372d38f..d2c0ba4830 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 c2c23e4155..6d6660d362 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 b54b6676d0..ec1c814a47 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 3e0873ee11..35f978e631 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 175370be90..de6e43bd79 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 01bb1dd29e..2885b42e68 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 8d064c1ef3..88f95bf308 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 e379c4cc3c..b819df44c2 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 18a1275ca6..de22ba0a46 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 c6226f5a5f..c965f8cd12 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 f44a76b71c..5c936e5f72 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 d556fff1be..a6e9f927c9 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 98afacb7d7..585c02bf9b 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 9c28bc3c69..37d66aabc2 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 85efaa5ad2..01af622932 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 33ed245739..c6517e866d 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 bc91f8595b..c758d49095 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 4d02ec8a8c..4bbc2c8fb8 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 084ee527af..284759e59e 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 8ed6c637fc..3fa0403bab 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 df1d0cf060..00bfb3f33b 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 c05c6f6cef..3f3e09242d 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/1789] 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 7fe441dfc6..abdf0f6067 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 cdc63fc012..f326b2d308 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 c5d60ea909..cd954ca6b0 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 8f7533ba5f..0a1f1e9e6a 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 dfa3218442..2d3920e771 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 9c526121e9..ebd25cde0d 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 04f4fbe8e7..d36306a1b5 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 a5d4571f76..7f68f17fa4 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 67b2785fa9..3daa5ee867 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 a799cd4351..1e5f7a32d6 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 e891b65fb6..47ff428789 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 7d5e4c3db9..37e0a4a5cd 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 026e305361..530fe86046 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 0b847b8ca6..c15f13bf24 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 7d2eb3b2a4..af63cc047d 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 68f2992096..38d8909e9b 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 d1458a27dd..adc436e584 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 2cf4226047..b30bab71cc 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 c80bb0950b..5ee4ab5cf0 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 3e0d793052..c75ebd2056 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 e10e74a53e..53cec9a0e6 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 719fd172d3..580595acd7 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 9e495b3163..3e275677e2 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 d579157160..fd103b4f6a 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 a3e8c5224f..16e55861bc 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 b109b13f91..741c7cc880 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 837c3fb890..04daf2ed55 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 34854d28cc..55d9e28d74 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 7723ee2151..bac485292b 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 04c356151d..971f593bfc 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 563ade3589..c5f597fcdc 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 e20c503c05..bcdf1032c7 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 6bde75bc6d..2facd03945 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 d22637d4e5..02c0099c4e 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 ce6d31b581..fc92e2b91d 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 e1b35ac7ee..0c4c0bd9dd 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 9d4ac3d682..7364ff54e9 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 01c310459b..df0fe21f2b 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 3075827a96..8d2ceddc98 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 50ecba6226..6b833656d7 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 5d320104a7..330735d9e4 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 d83e9f78ad..c26d550049 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 46920122a6..baab09248b 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 bc4591b9a8..5d19e4950f 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 d1f4a1bd82..df33262d8d 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 e31033185e..0beb301d46 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 42c5fe8505..a0cde666e5 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 a8404ef99f..475f9a83cb 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 c62a11b150..e910bd5ea1 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 68e7855bb7..19cfcbaaea 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 751a69a41a..3db54618e5 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 5d24f86f52..6784622a2a 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 caabc319bf..8e4a2de7d1 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 9066351d9f..b8bd415b45 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 09606087f1..dca332f50e 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 a7ea4dc133..1be24ff645 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 8b4d51142a..e62a04d409 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 487857213d..94f0d95379 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 f472eab562..2d830e1501 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 83df24904f..01fe08f354 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 c0b0df2cf5..a2371b6434 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 b988a7883a..22140eba68 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 a23e3445d8..11c3087222 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 a9134026ea..32373fcf7a 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 a0f33f1841..e308b37687 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 c670914835..1bf6d9854b 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 28995db8ea..78faf2d1d3 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 7255d64887..84dc243c24 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 4a0141b106..b4dcd8bb7e 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 655e77ddd3..2e24c9dcbd 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 9437b82485..af236b4f2d 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 26551ff830..e5283ba68e 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 0df0dc011c..0dd32830cd 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 2f90e08c22..5a99743a18 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/1789] 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 abdf0f6067..a81c5f69df 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 5ccc516e42..57a3284f76 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 bcaf00ec10..dbfa2ebf42 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 79604a5845..ff5af37169 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 d5c0197dd6..192b909251 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 ba7c6c618a..e8c6fa2d91 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 b45ce15acd..c7ab2604a8 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 ade6e1d894..be9ca1982d 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 675740d99a..a95dbe2a51 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 20450f9813..07e36fb1b3 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 5b97a115ea..f6ea231e84 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 38f1ceb616..5a6afabe70 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 34dfd1efe1..f802763704 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 e02bc5ed2c..1f844ff7cd 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 257883af2c..7d1b0ad824 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 3bfcfe862c..61ba6aca5d 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 7c8129a3cc..30a6e1df0e 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 1abd672f68..f47fe54bba 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 ed9b43b6be..d48519b735 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 b0737cbcc9..3d1318fcd8 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 0ec112131f..21284c9129 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 2bdb4ff94f..9aa0b151bc 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 2fd49e7cfa..a0558bcbeb 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 74a7d0efaa..5cd5cd9cb9 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 6eb1f57604..8d78fa4e1e 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 9e687100f7..1195311fd2 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 f71d4dcf8a..e0404ad629 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 bd857f7489..b97c230f79 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 e91391e8bb..9425eb8849 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 994b3943f2..e61965ca3a 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 3e9c477cb3..25b57e10e6 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 c0a4936bdc..0ed2f74166 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 afa672f983..31eb219ad8 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 d01dbf75a3..cd8d7192bf 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 1767c02fbb..f5c6147b13 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 6fca3226a5..1a2b49ee38 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 34f66bb8bb..a52dbe86f5 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 4807453f2a..f0e922deec 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 a4424b69ac..4bd8d724ad 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 9753863557..244cd1ba4f 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 f48a8abc7a..af7cfda155 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 c32491fe42..d38279a659 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 56c0ac529e..8fcc1569d2 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 25b485dfad..69f539f373 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 9865d3d1ce..a0870f7ce3 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 f42cb7e7d5..07dfafea6b 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 8588a1a57a..d9d6e10c99 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/1789] 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 a81c5f69df..207d0b7ae1 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 f802763704..34dfd1efe1 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 f326b2d308..39042f853b 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 d20a8f4b90..bfb9bc66c3 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 8d78fa4e1e..6eb1f57604 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/1789] 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 207d0b7ae1..429672258b 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 aa09192666..a599a83efa 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 39042f853b..a02d84bf48 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 0000000000..2bfc4ac2ad --- /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 0d0af6673c..ded54328e9 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 ffba6df404..636e786ea3 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 9234d1a4bd..e758ce8efe 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 a267a71129..921f9a9590 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 9535501fb4..379700d04f 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 270201cc4e..d12d4121c8 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/1789] 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 ccffd14290..7856659157 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/1789] 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 429672258b..d71681c9c9 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 16fbe58dec..78d5f870ea 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 a2730deb3d..4aff628e4c 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 0e798eb5c0..b3cda234d7 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 0c62d0c7c4..38d7f2c1f0 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 ad776bb175..8eca193023 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 93cce45dd9..8091bdeda3 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 c9435f826e..81e284c02f 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 291ede59b4..2436a3b202 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 7196d50093..7a24da4d7d 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 f9be2fd5c3..eb9c212432 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 455c341e4e..7c4542f506 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 209e72730d..f201e3fc3c 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 a7da560658..a1c554ba8e 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 386c903281..222641aa62 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 cca8bf4e3a..37953cdf26 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 e353d31af3..5be0b256fc 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 5276d0eb47..10973e4974 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 cc3821d9f2..40b0b16ad3 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/1789] 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 d71681c9c9..c15ac39eb3 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 e0d523603f..8f00712e05 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 c77059e214..68dc4b7021 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/1789] 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 c15ac39eb3..ead664995a 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 0000000000..84f424fb71 --- /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/1789] 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 ead664995a..91b787fb8e 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 284be58a82..06cf7ae5ef 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 df06d98b43..80a05fe837 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/1789] 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 91b787fb8e..654fba3695 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 84f424fb71..b8178f99e6 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/1789] 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 654fba3695..293ca28e0b 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 b8178f99e6..227d578754 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 e60cede66d..fab2c15313 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 4aa6d510da..5e2c6a3fb3 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 8fe066e60b..01801a49aa 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 f1f9f032a3..217c42443b 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 7ce8b7a27e..ba23578483 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/1789] 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 293ca28e0b..cdba212a4f 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 06cf7ae5ef..284be58a82 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 80a05fe837..df06d98b43 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/1789] 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 cdba212a4f..1e1a39ace9 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 df06d98b43..80a05fe837 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/1789] 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 284be58a82..ceb87242c2 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 80a05fe837..3b6c27320a 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/1789] 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 1e1a39ace9..293ca28e0b 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 ceb87242c2..701e474fe5 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/1789] 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 7c117d13f5..437bfcf1cc 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/1789] 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 252d705fdf..f0e44b822d 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 1590a29d66..0931c2492a 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 14a7f79e42..8a72947e2c 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 510125a9ba..50e7c470af 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 337bd91212..e19f55e81f 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 677851641a..e245a05f97 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/1789] 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 856bb90170..cbd92c0d3f 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/1789] 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 f0e44b822d..d51170684a 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 0931c2492a..b6df58b5f8 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 8a72947e2c..0f3e8336db 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 50e7c470af..09f2b6b71c 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/1789] 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 d51170684a..cb5ed5c78e 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 b6df58b5f8..d0bf46d5cc 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 0f3e8336db..a85ecea67d 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 09f2b6b71c..e77ec8b28a 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/1789] 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 0cf91034a3..98493f0737 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 e19f55e81f..e91dbb6c6a 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 e245a05f97..af66180aeb 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/1789] 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 83086ff40f..47d3d5c251 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 03c1501ad4..97e7a2b823 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/1789] 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 0000000000..5a582a24a6 --- /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 3d099857e2..1e7f00b736 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/1789] 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 47d3d5c251..83086ff40f 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 97e7a2b823..017eaf08be 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/1789] 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 2d3c531559..312b95c981 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/1789] 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 633b1824b4..6b8284669e 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/1789] 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 0000000000..9eb3469756 --- /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 0000000000..f47d82502d --- /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 0000000000..ded63b6bb2 --- /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 0000000000..cdc253ee26 --- /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 0000000000..02a1ba1199 --- /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 0000000000..93a02b903a --- /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 0000000000..94b105f908 --- /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 0000000000..9ad81cdba4 --- /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 0000000000..2d70326499 --- /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 0000000000..80de64966b --- /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 0000000000..b9fdbb69ef --- /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 0000000000..c5746ae657 --- /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 0000000000..e018575818 --- /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 0000000000..9c83ccaa71 --- /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 0000000000..8675da798d --- /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 0000000000..a1d75fd594 --- /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 0000000000..0161fbe77e --- /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 0000000000..ffbe00fb11 --- /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 0000000000..90055920f2 --- /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 0000000000..78341db1dd --- /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 0000000000..eb0bb13f09 --- /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 0000000000..c11b12daf8 --- /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 0000000000..046ea44bc5 --- /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 0000000000..562b87b729 --- /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 0000000000..6abb2d23b6 --- /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 0000000000..59411c26df --- /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 0000000000..f9a272fbb0 --- /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 0000000000..c46cff3490 --- /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 0000000000..746ffca4e2 --- /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 0000000000..963d9c68a5 --- /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 0000000000..48a048d5de --- /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 0000000000..a758505334 --- /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 0000000000..3a68cef638 --- /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 0000000000..606f3f7e67 --- /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 0000000000..25725e5026 --- /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 0000000000..7d0631e541 --- /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 0000000000..711079460f --- /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 0000000000..c2004b1390 --- /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 0000000000..cd2a8a5254 --- /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 0000000000..6d16f8f6e2 --- /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 0000000000..063bf99ee1 --- /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 0000000000..a79f416b32 --- /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 0000000000..685e06977f --- /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 0000000000..2995cf991b --- /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 0000000000..f741ded701 --- /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 0000000000..b6af9245d0 --- /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 0000000000..db5b302e1a --- /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 0000000000..603128daea --- /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 0000000000..601584bf81 --- /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 0000000000..d04128179d --- /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 0000000000..580e7b3f45 --- /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 0000000000..7b6812c711 --- /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 0000000000..beda4eb54e --- /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 0000000000..4f3d9079d7 --- /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 0000000000..574134cace --- /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 0000000000..f6da8b7bac --- /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 0000000000..e7c1d41c77 --- /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 0000000000..8f9291d2e9 --- /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 0000000000..2585591107 --- /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 0000000000..1822609303 --- /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 0000000000..6ea594080f --- /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 0000000000..7c8f370035 --- /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 0000000000..9d66058153 --- /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 0000000000..ba54259750 --- /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 0000000000..148045815f --- /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/1789] 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 83086ff40f..8397cdaf5d 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 017eaf08be..f5f46a4fcb 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 293ca28e0b..357a2e1d05 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 a07ea29556..d5a0f76ed1 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 61c151aa52..c0f0c411e3 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 1ad85c60be..5147fb0d97 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 9e16ae4360..7256e6854e 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 0000000000..6bc05db91d --- /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 c7ab2604a8..3afc2a59a6 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 312b95c981..b8dae98d38 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 0000000000..7e423cc96d --- /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 0000000000..a6de9b8f1f --- /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 026069146a..8edc42c7f8 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 9877e01b77..7f743fe95a 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/1789] 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 3afc2a59a6..f8b35d4aae 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 0000000000..c4f9080d62 --- /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/1789] 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 8eea5bacec..9e4835d376 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 c0f0c411e3..c3d3877b13 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 7256e6854e..340039bf4a 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 529e1ae4a4..3358c9e63a 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 cbd92c0d3f..cdb919da1f 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 761aa267ae..2b43724ff0 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 b8dae98d38..f7a4ba6138 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 d78b3acf3a..462bb3df6e 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 1df8f71a96..5df3f8bf15 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 ceec5f7cfe..2ae6fc15d8 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 fd48030cf2..e40d656021 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 54e1da4c41..f2d8127b5c 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 0fa30e1755..6928dadffa 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 b88724fbc7..7e8bd11edd 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 e5cf90566f..0f51442a56 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 62bba62a2b..400c21bf5a 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 3d1318fcd8..f7b1466f8e 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 21284c9129..bbd3d0929a 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 afc50f087d..c4b152cd8b 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 84985abbce..137941d004 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 7c11efcda2..45c0fd4bff 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 d6024cf323..a4a36a7789 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 76be89b493..91fc46229d 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 19144986c4..157fc89ada 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 f032585242..8f63f01931 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 01f69e3ccb..73293225ee 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 8c4ea07439..23d8f50e63 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 a176905563..db654923c2 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 2b15a4eb65..2178b579fb 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 2e860a391f..597b299278 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 e5b561af65..dd589494e6 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 239e732056..907991fb75 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 473036edb1..05d5db8cb4 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 f7907395ad..80a254073b 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 4e38e44bcb..85ea054c4e 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 1009a53a0c..8512285772 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 8bcc663327..d8a02005d6 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 1e3b55b0ee..3e1383bdb4 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 f099277097..a8b573809a 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 6600512da1..22c57206f2 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 c006d9b84b..c10dd28082 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 3fee19deb3..5688e62df7 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 a703decac4..bbde2f2aa8 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 b0a39b8ccf..292557f12a 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 fb36d04c44..a4a848f545 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 1093e24cb4..302b93a8c8 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 d3f6af2137..3cd18f611d 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 e38e5be35a..92ebb92cf1 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 0c58b3b41e..8236a7094e 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 dd4764ee66..0471bc249c 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 fcb6fcd893..0dd713bee8 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 fd30ee8bd8..bd52f45911 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 e98bdb9626..edaead6006 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 fd103b4f6a..6674b65374 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 80a49d7cfc..5b4a28b2ec 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 37d66aabc2..76d74b8477 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 5a99743a18..832bb70140 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 4ed9c79c1c..3e83e3d819 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 9db20e5b34..61e4c7925e 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 382e4208ad..93fe12d555 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 0f1778aa21..44fa96ab15 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 e6cf82bf5f..1b460f8b68 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 68ab00e39c..41c85112a7 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 b7f4e23bf0..34b7c46232 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 6ee268c32a..329b84522c 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 da7d60bd3d..c8509d48c8 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 ace5a21c5b..546eda16f8 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 6618b6effe..4fb960aa66 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 ff1626d7d2..a2baee1e86 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 1ada3c4624..4384ae77b0 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 b26eda5849..0c39f2271f 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 e701997c6c..fe02aeb58e 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 a59308f40c..739267f0e5 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 ccaaa4ca78..5602adf7b9 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 b047b86a69..2fca7b7d97 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 3ffc8f9db7..5322385963 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 9770a62a72..758ecbbb69 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 c630063120..8947396deb 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 0ff71d5025..220df284b8 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/1789] 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 af66180aeb..2461381361 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/1789] 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 2461381361..57f9032dc4 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/1789] 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 b143c3e446..faa4cb81a1 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 0000000000..97bae38991 --- /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 846188b985..52dec8da3b 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 cdb919da1f..ea86730f30 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/1789] 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 faa4cb81a1..b143c3e446 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 97bae38991..0000000000 --- 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 52dec8da3b..846188b985 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 ea86730f30..cdb919da1f 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/1789] 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 57f9032dc4..73238849d2 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/1789] Makefile target for `--fix` --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 9e007ae2cc..273283a4af 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/1789] 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 227d578754..d1adbb4ae5 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 f8b35d4aae..439250fff4 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 f7a4ba6138..b550210254 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 8edc42c7f8..792d7acf57 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 7f743fe95a..8855ddd8f6 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/1789] 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 846188b985..52dec8da3b 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 cdb919da1f..051d15d960 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 1a84e5e170..0c361880e7 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 0000000000..dbba869398 --- /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/1789] 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 0000000000..45c74f1027 --- /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 1e7f00b736..ffbbcfecac 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 0000000000..bf5da59ef4 --- /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 0000000000..d27bdc4de4 --- /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/1789] 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 3be9b7193d..53988eac7a 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 a85b6a4709..f7f7fceb12 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/1789] 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 d5a0f76ed1..2f815c0233 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 b550210254..d71b29187b 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 0000000000..5630b13e48 --- /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/1789] `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 5630b13e48..89eaf7ac6f 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 2717939b30..e1f377f66e 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 a202992b52..b68de7a821 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 792d7acf57..0b82bfb8fb 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 bf5da59ef4..2ace8bc077 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/1789] 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 0b82bfb8fb..c718a8f9c9 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 2ace8bc077..c4282bf4d2 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 0000000000..b85af02799 --- /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/1789] 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 273283a4af..9a00fc8586 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 d1adbb4ae5..1d6457e123 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 6bc05db91d..af8755b2f8 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 439250fff4..a92c110fac 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 b68de7a821..bbec610dd5 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/1789] 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 1d6457e123..227d578754 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 a92c110fac..5b5b9b4726 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 bbec610dd5..197a58b442 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/1789] 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 8397cdaf5d..bb72a6ab2f 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 f5f46a4fcb..767a57642b 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/1789] 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 e77ec8b28a..8009e7d629 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/1789] 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 5147fb0d97..e285b04a14 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/1789] 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 c718a8f9c9..36c383bc56 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/1789] 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 d71b29187b..f56c6ce59a 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/1789] 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 f69eca834e..0c3b11d4a9 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/1789] 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 357a2e1d05..ff55498901 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 f761a5a47e..a37dc42129 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 a02d84bf48..b09bb8b112 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 8544459f9c..c172ff021c 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/1789] 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 45c74f1027..fda84e6d16 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 c4282bf4d2..c44c4419e5 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/1789] 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 98493f0737..a468bd523a 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/1789] 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 15654b55f6..cc8ddbac5d 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/1789] 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 ff55498901..5b98342605 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 83315b6e0c..5df7f61e07 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/1789] 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 fda84e6d16..250ef7ba97 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 d5ae8bada3..112fb17e4c 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 5b5b9b4726..c4985a4ad3 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 9eb3469756..bdb5c973f1 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 db53567e7b..ec48db9cf1 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 f47d82502d..bd003c4f5a 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 ded63b6bb2..ba607b957e 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 cdc253ee26..481c6f0bcf 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 02a1ba1199..11f52de24a 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 93a02b903a..47925389f3 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 94b105f908..d40a1721d6 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 9ad81cdba4..5713a025ba 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 2d70326499..1a08b2cf53 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 80de64966b..f23ae4b9e7 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 b9fdbb69ef..47a80d87d8 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 c5746ae657..802779e560 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 e018575818..284a5700c5 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 9c83ccaa71..24052dc8c9 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 8675da798d..038f19d079 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 a1d75fd594..a2a71b9aca 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 0161fbe77e..638054207c 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 ffbe00fb11..008cfef2de 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 90055920f2..840450cce0 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 78341db1dd..79eb32732d 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 eb0bb13f09..ccc7d2f3e8 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 c11b12daf8..7f106fec80 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 046ea44bc5..bd01419ffb 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 562b87b729..17e1855459 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 6abb2d23b6..ed0f4965f7 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 59411c26df..2acb39c865 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 f9a272fbb0..128b918743 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 c46cff3490..601aaf38c8 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 746ffca4e2..61f5965218 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 963d9c68a5..14dd4f7a9d 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 48a048d5de..a76455ad71 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 a758505334..d6b044c426 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 3a68cef638..55b9f26662 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 606f3f7e67..452a3d7195 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 25725e5026..0c662f1da3 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 7d0631e541..e2b5af378c 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 711079460f..c9ed6c0901 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 c2004b1390..2ecf6acaad 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 cd2a8a5254..a04fc867b2 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 6d16f8f6e2..697c9eb673 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 063bf99ee1..8ee9144bc4 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 a79f416b32..e007d8ed6c 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 685e06977f..6e56086b32 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 2995cf991b..a9e6203b31 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 f741ded701..01c8202957 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 b6af9245d0..e78f445f2b 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 db5b302e1a..2bdc632dc0 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 603128daea..d43a6004fa 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 601584bf81..b3287c9a05 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 d04128179d..06ef862f28 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 580e7b3f45..633cee8a5c 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 7b6812c711..42c1706973 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 beda4eb54e..bbbdb5a38f 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 4f3d9079d7..b80cc0c36d 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 574134cace..aaf1c80dee 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 f6da8b7bac..1d4abc4332 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 e7c1d41c77..5ee0033dc2 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 8f9291d2e9..65d853915a 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 2585591107..e5762ee32b 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 1822609303..369e7cbee3 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 6ea594080f..0a482fa833 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 7c8f370035..ed86576c33 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 9d66058153..b77d8060f8 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 ba54259750..17fad64bc1 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 148045815f..61d6fbd505 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/1789] 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 018e1da4f8..85b313bb03 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 0000000000..1889040248 --- /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 0000000000..512d7a6bcf --- /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/1789] 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 c4985a4ad3..dc211b0532 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/1789] 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 5a582a24a6..1ff8295289 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 0000000000..94953f813d --- /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 0000000000..12ca9cbcea --- /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/1789] 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 1889040248..4a09327a38 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/1789] 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 340039bf4a..6c07b2cc99 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 8c070bb040..23b25a5539 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 b7834e713f..4d03716b02 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/1789] 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 dc211b0532..146c5d4acb 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 a6de9b8f1f..345869fcb3 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/1789] 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 146c5d4acb..0176d537a9 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 0000000000..1b315b32b8 --- /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 c44c4419e5..e71c37e617 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 0000000000..f53c87832c --- /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 0000000000..9cec06b484 --- /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/1789] 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 da37080cfa..1cdd33ebf7 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 2b43724ff0..12bd0d2153 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 0000000000..2963034496 --- /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/1789] 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 250ef7ba97..14895bed7e 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 e71c37e617..72c5e506a9 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 d27bdc4de4..db471495aa 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 b85af02799..f216ff85fd 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/1789] 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 1cdd33ebf7..4a4844cd8c 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 72c5e506a9..596023259c 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 db471495aa..d8db59f36f 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 f216ff85fd..cf1838943c 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/1789] 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 6c07b2cc99..340039bf4a 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 23b25a5539..8c070bb040 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 4d03716b02..b7834e713f 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/1789] 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 340039bf4a..0c1f208d82 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/1789] 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 0c3b11d4a9..b195e03a34 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 4384ae77b0..4515aefa07 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/1789] 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 8c070bb040..18ea0112e9 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 8d050e7636..4260da643c 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 0000000000..ea4674dd3f --- /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 0000000000..3fecc5f91d --- /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 0000000000..1682c7c22a --- /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 0000000000..b202edd3f2 --- /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 0000000000..c423628f8c --- /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 0000000000..e1aa5c372d --- /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 f8268f8fcd..1e651b6b48 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 0000000000..0d780ce1a0 --- /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 0000000000..5e462a1cbe --- /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/1789] 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 767a57642b..889108c2eb 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/1789] 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 14895bed7e..b44a10ddea 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 596023259c..45647bf7d8 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 0000000000..61f1c151d2 --- /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 0000000000..2cf2934112 --- /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/1789] 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 f4c9cd19fc..a57224a0bb 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 6136b1c068..fd7076124f 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 ba2ce7e3be..eca2976cda 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/1789] 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 2dcb9d3cc3..50b72cbaeb 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 c63d2ea3eb..23df75cf0a 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 0000000000..974d6598ee --- /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 0000000000..749dad7abe --- /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/1789] 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 cee351e3ff..133aef4099 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/1789] 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 08d678152a..03255de9d9 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 e49a6100f7..f981f92621 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 0000000000..b549d338fe --- /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/1789] 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 ffbbcfecac..ce7a3a0b37 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 cd785677b1..005f72b3eb 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 0000000000..bb562dfb19 --- /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 0000000000..e7629e4f83 --- /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/1789] 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 133aef4099..9381a36922 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 e97673ff5f..40f16771aa 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 0000000000..c6061a6ba9 --- /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 0000000000..9403342cea --- /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/1789] 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 c6061a6ba9..d96c10debf 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 9403342cea..353d3f9cd2 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/1789] Fix build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9a00fc8586..a780c4306f 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/1789] 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 bb72a6ab2f..15d646841b 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 889108c2eb..23b8f47b2b 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/1789] 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 15d646841b..116c24c5d7 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 23b8f47b2b..f2982c077f 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/1789] 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 e91dbb6c6a..b75d11494f 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 73238849d2..940847e80a 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/1789] Fix --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index f2982c077f..3e1cf79ba6 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/1789] 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 0000000000..f06235c460 --- /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 0000000000..3f3524c9cc --- /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 0000000000..d7f902e855 --- /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 0000000000..8f4f624afc --- /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 0000000000..e809b2ee73 --- /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 0000000000..62ac6dae38 --- /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 185fc779b4..0000000000 --- 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 a468bd523a..2c51d6bbd3 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/1789] 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 0000000000..0f3c22cbfc --- /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 2c51d6bbd3..a33076e52c 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/1789] Remove hack for prefixed global functions --- bin/phpstan | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/bin/phpstan b/bin/phpstan index 119af4377c..23e5725d38 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/1789] 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 a33076e52c..a9ca6a2816 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 0000000000..61ead86667 --- /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 0000000000..4ea8c3e23c --- /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 0000000000..f83b3a8b2d --- /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 0000000000..5f993cc80e --- /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