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 @@ +