Skip to content

Commit e1657b4

Browse files
ondrejmirtesclaude
andcommitted
Inject analysis settings into statement handlers instead of reading them from NodeScopeResolver
IfHandler, ForHandler, WhileHandler, DoWhileHandler and ForeachHandler receive treatPhpDocTypesAsCertain, polluteScopeWithLoopInitialAssignments and polluteScopeWithAlwaysIterableForeach as #[AutowiredParameter]s, and ClassLikeHandler receives the ReadWritePropertiesExtension collection. NodeScopeResolver drops the accessors and the constructor parameters. Tests configure these through getAdditionalConfigFiles() now: cases that ran with non-default values move into sibling test classes backed by shared neon files, and the ReadWritePropertiesExtension doubles become services. RuleTestCase::getReadWritePropertiesExtensions(), shouldPolluteScopeWithLoopInitialAssignments() and shouldPolluteScopeWithAlwaysIterableForeach() no longer configure the analysis - overriding them with a value that differs from the configuration fails the test instead of silently changing its meaning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014hgcbwfKQnye2coQXtX5XU
1 parent fc0361a commit e1657b4

41 files changed

Lines changed: 2425 additions & 2035 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/Analyser/NodeScopeResolver.php‎

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
use PHPStan\Analyser\Generics\TemplateArgumentObserver;
2828
use PHPStan\Analyser\Generics\TemplateArgumentStats;
2929
use PHPStan\DependencyInjection\AutowiredExtensions;
30-
use PHPStan\DependencyInjection\AutowiredParameter;
3130
use PHPStan\DependencyInjection\AutowiredService;
3231
use PHPStan\DependencyInjection\Container;
3332
use PHPStan\DependencyInjection\ExtensionsCollection;
@@ -41,7 +40,6 @@
4140
use PHPStan\Node\StaticMethodCallExpressionNode;
4241
use PHPStan\Reflection\Native\NativeMethodReflection;
4342
use PHPStan\Reflection\Php\PhpMethodReflection;
44-
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
4543
use PHPStan\ShouldNotHappenException;
4644
use PHPStan\Type\ErrorType;
4745
use PHPStan\Type\MixedType;
@@ -116,23 +114,14 @@ class NodeScopeResolver
116114
public static array $guardProcessedExprIds = [];
117115

118116
/**
119-
* @param ExtensionsCollection<ReadWritePropertiesExtension> $readWritePropertiesExtensions
120117
* @param ExtensionsCollection<PerFileAnalysisResettable> $perFileAnalysisResettables
121118
*/
122119
public function __construct(
123120
private readonly Container $container,
124121
private readonly TemplateArgumentObserver $templateArgumentObserver,
125122
private readonly FileHelper $fileHelper,
126-
#[AutowiredExtensions(of: ReadWritePropertiesExtension::class)]
127-
private readonly ExtensionsCollection $readWritePropertiesExtensions,
128123
#[AutowiredExtensions(of: PerFileAnalysisResettable::class)]
129124
private readonly ExtensionsCollection $perFileAnalysisResettables,
130-
#[AutowiredParameter]
131-
private readonly bool $polluteScopeWithLoopInitialAssignments,
132-
#[AutowiredParameter]
133-
private readonly bool $polluteScopeWithAlwaysIterableForeach,
134-
#[AutowiredParameter]
135-
private readonly bool $treatPhpDocTypesAsCertain,
136125
private readonly ExpressionResultFactory $expressionResultFactory,
137126
private readonly StatementsHandler $statementsHandler,
138127
)
@@ -431,27 +420,6 @@ public function isAnalysedFile(string $fileName): bool
431420
return isset($this->analysedFiles[$fileName]);
432421
}
433422

434-
public function shouldPolluteScopeWithLoopInitialAssignments(): bool
435-
{
436-
return $this->polluteScopeWithLoopInitialAssignments;
437-
}
438-
439-
public function shouldPolluteScopeWithAlwaysIterableForeach(): bool
440-
{
441-
return $this->polluteScopeWithAlwaysIterableForeach;
442-
}
443-
444-
public function shouldTreatPhpDocTypesAsCertain(): bool
445-
{
446-
return $this->treatPhpDocTypesAsCertain;
447-
}
448-
449-
/** @return ExtensionsCollection<ReadWritePropertiesExtension> */
450-
public function getReadWritePropertiesExtensions(): ExtensionsCollection
451-
{
452-
return $this->readWritePropertiesExtensions;
453-
}
454-
455423
/** Whether an on-demand walk answers already processed real nodes from their stored results (see processExprOnDemand()). */
456424
public function isReturningStoredExpressionResults(): bool
457425
{

‎src/Analyser/StmtHandler/ClassLikeHandler.php‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
use PHPStan\BetterReflection\Reflector\Reflector;
2323
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
2424
use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
25+
use PHPStan\DependencyInjection\AutowiredExtensions;
2526
use PHPStan\DependencyInjection\AutowiredService;
27+
use PHPStan\DependencyInjection\ExtensionsCollection;
2628
use PHPStan\File\FileReader;
2729
use PHPStan\Node\ClassConstantsNode;
2830
use PHPStan\Node\ClassMethodsNode;
@@ -32,6 +34,7 @@
3234
use PHPStan\Reflection\ClassReflection;
3335
use PHPStan\Reflection\ClassReflectionFactory;
3436
use PHPStan\Reflection\ReflectionProvider;
37+
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
3538
use PHPStan\ShouldNotHappenException;
3639
use function sprintf;
3740
use function usort;
@@ -46,12 +49,17 @@
4649
final class ClassLikeHandler implements StmtHandler
4750
{
4851

52+
/**
53+
* @param ExtensionsCollection<ReadWritePropertiesExtension> $readWritePropertiesExtensions
54+
*/
4955
public function __construct(
5056
private Reflector $reflector,
5157
private ClassReflectionFactory $classReflectionFactory,
5258
private CalledMethodProcessor $calledMethodProcessor,
5359
private ReflectionProvider $reflectionProvider,
5460
private AttributesHandler $attributesHandler,
61+
#[AutowiredExtensions(of: ReadWritePropertiesExtension::class)]
62+
private ExtensionsCollection $readWritePropertiesExtensions,
5563
)
5664
{
5765
}
@@ -129,7 +137,7 @@ public function processStmt(
129137
// Class members have their own inference context, including when the class
130138
// declaration is visited during an enclosing body's observation pass.
131139
$nodeScopeResolver->processStmtNodesInternal($stmt, $classLikeStatements, $classScope, $storage, $classStatementsGatherer, StatementContext::createTopLevel());
132-
$nodeScopeResolver->callNodeCallback($nodeCallback, new ClassPropertiesNode($stmt, $nodeScopeResolver->getReadWritePropertiesExtensions(), $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope, $storage);
140+
$nodeScopeResolver->callNodeCallback($nodeCallback, new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensions, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope, $storage);
133141
$nodeScopeResolver->callNodeCallback($nodeCallback, new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls(), $classReflection), $classScope, $storage);
134142
$nodeScopeResolver->callNodeCallback($nodeCallback, new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches(), $classReflection), $classScope, $storage);
135143
$classReflection->evictPrivateSymbols();

‎src/Analyser/StmtHandler/DoWhileHandler.php‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Analyser\StatementContext;
1717
use PHPStan\Analyser\StmtHandler;
1818
use PHPStan\Analyser\VariableFlow;
19+
use PHPStan\DependencyInjection\AutowiredParameter;
1920
use PHPStan\DependencyInjection\AutowiredService;
2021
use PHPStan\Node\DoWhileLoopConditionNode;
2122
use function array_merge;
@@ -28,6 +29,13 @@
2829
final class DoWhileHandler implements StmtHandler
2930
{
3031

32+
public function __construct(
33+
#[AutowiredParameter]
34+
private bool $treatPhpDocTypesAsCertain,
35+
)
36+
{
37+
}
38+
3139
public function supports(Stmt $stmt): bool
3240
{
3341
return $stmt instanceof Do_;
@@ -137,7 +145,7 @@ public function processStmt(
137145

138146
$alwaysIterates = false;
139147
if ($context->isTopLevel()) {
140-
$condBooleanType = ($nodeScopeResolver->shouldTreatPhpDocTypesAsCertain() ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
148+
$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
141149
$alwaysIterates = $condBooleanType->isTrue()->yes();
142150
}
143151

‎src/Analyser/StmtHandler/ForHandler.php‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use PHPStan\Analyser\TypeSpecifierContext;
2626
use PHPStan\Analyser\VariableFlow;
2727
use PHPStan\Analyser\VariableFlowBuilder;
28+
use PHPStan\DependencyInjection\AutowiredParameter;
2829
use PHPStan\DependencyInjection\AutowiredService;
2930
use PHPStan\TrinaryLogic;
3031
use function array_last;
@@ -41,6 +42,15 @@
4142
final class ForHandler implements StmtHandler
4243
{
4344

45+
public function __construct(
46+
#[AutowiredParameter]
47+
private bool $polluteScopeWithLoopInitialAssignments,
48+
#[AutowiredParameter]
49+
private bool $treatPhpDocTypesAsCertain,
50+
)
51+
{
52+
}
53+
4454
public function supports(Stmt $stmt): bool
4555
{
4656
return $stmt instanceof For_;
@@ -166,7 +176,7 @@ public function processStmt(
166176
// only the last condition expression is relevant whether the loop continues
167177
// see https://www.php.net/manual/en/control-structures.for.php
168178
if ($condExpr === $lastCondExpr) {
169-
$condTruthiness = ($nodeScopeResolver->shouldTreatPhpDocTypesAsCertain() ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
179+
$condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
170180
$isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue());
171181
}
172182

@@ -285,20 +295,20 @@ public function processStmt(
285295
}
286296

287297
if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating() || ($backEdgeDead && count($breakExitPoints) === 0)) {
288-
if ($nodeScopeResolver->shouldPolluteScopeWithLoopInitialAssignments()) {
298+
if ($this->polluteScopeWithLoopInitialAssignments) {
289299
$finalScope = $initScope;
290300
} else {
291301
$finalScope = $scope;
292302
}
293303

294304
} elseif ($isIterableAtLeastOnce->maybe()) {
295-
if ($nodeScopeResolver->shouldPolluteScopeWithLoopInitialAssignments()) {
305+
if ($this->polluteScopeWithLoopInitialAssignments) {
296306
$finalScope = $finalScope->mergeWith($initScope);
297307
} else {
298308
$finalScope = $finalScope->mergeWith($scope);
299309
}
300310
} else {
301-
if (!$nodeScopeResolver->shouldPolluteScopeWithLoopInitialAssignments()) {
311+
if (!$this->polluteScopeWithLoopInitialAssignments) {
302312
$finalScope = $finalScope->mergeWith($scope);
303313
}
304314
}

‎src/Analyser/StmtHandler/ForeachHandler.php‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public function __construct(
8585
private bool $implicitThrows,
8686
private VarAnnotationProcessor $varAnnotationProcessor,
8787
private AssignHandler $assignHandler,
88+
#[AutowiredParameter]
89+
private bool $polluteScopeWithAlwaysIterableForeach,
8890
)
8991
{
9092
}
@@ -151,7 +153,7 @@ public function processStmt(
151153
// walked synthetic would delegate to); the walk is the composition's
152154
// miss seam
153155
$nonEmptyIterateeScope = $scope;
154-
if ($nodeScopeResolver->shouldPolluteScopeWithAlwaysIterableForeach()) {
156+
if ($this->polluteScopeWithAlwaysIterableForeach) {
155157
$identicalNarrowingHelper = $this->container->getByType(IdenticalNarrowingHelper::class);
156158
$emptyArrayType = new ConstantArrayType([], []);
157159
$nonEmptyTypes = $identicalNarrowingHelper->specifyIdenticalAgainstType(
@@ -460,7 +462,7 @@ static function () use ($condResult, $emptyArrayType): Type {
460462
), TypeSpecifierContext::createTruthy()));
461463
} elseif ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) {
462464
$finalScope = $scope;
463-
} elseif (!$nodeScopeResolver->shouldPolluteScopeWithAlwaysIterableForeach()) {
465+
} elseif (!$this->polluteScopeWithAlwaysIterableForeach) {
464466
$finalScope = $scope->processAlwaysIterableForeachScopeWithoutPollute($finalScope);
465467
// get types from finalScope, but don't create new variables
466468
}
@@ -484,7 +486,7 @@ static function () use ($condResult, $emptyArrayType): Type {
484486
VariableFlowBuilder::targetWrite($stmt->valueVar, VariableWrite::KIND_FOREACH_VALUE, $finalScope, $storage),
485487
$stmt->byRef && $stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name) ? VariableFlow::escape($stmt->valueVar->name) : null,
486488
);
487-
$loopFlow = VariableFlow::loop($traversableThrowPoint !== null ? VariableFlow::throwing($traversableThrowPoint->getType(), true) : null, VariableFlow::sequence($bindingFlow, $finalScopeResult->getVariableFlow()), null, $isIterableAtLeastOnce->yes() && $nodeScopeResolver->shouldPolluteScopeWithAlwaysIterableForeach(), true);
489+
$loopFlow = VariableFlow::loop($traversableThrowPoint !== null ? VariableFlow::throwing($traversableThrowPoint->getType(), true) : null, VariableFlow::sequence($bindingFlow, $finalScopeResult->getVariableFlow()), null, $isIterableAtLeastOnce->yes() && $this->polluteScopeWithAlwaysIterableForeach, true);
488490
$bindingWrites = VariableFlowBuilder::writes($bindingFlow);
489491
$bindings = [];
490492
foreach ($bindingWrites as $write) {

‎src/Analyser/StmtHandler/IfHandler.php‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Analyser\StatementContext;
1414
use PHPStan\Analyser\StmtHandler;
1515
use PHPStan\Analyser\VariableFlow;
16+
use PHPStan\DependencyInjection\AutowiredParameter;
1617
use PHPStan\DependencyInjection\AutowiredService;
1718
use function array_merge;
1819
use function array_reverse;
@@ -25,6 +26,13 @@
2526
final class IfHandler implements StmtHandler
2627
{
2728

29+
public function __construct(
30+
#[AutowiredParameter]
31+
private bool $treatPhpDocTypesAsCertain,
32+
)
33+
{
34+
}
35+
2836
public function supports(Stmt $stmt): bool
2937
{
3038
return $stmt instanceof If_;
@@ -44,7 +52,7 @@ public function processStmt(
4452
$elseFlow = null;
4553
$condResult = $nodeScopeResolver->processExprNode($stmt, $stmt->cond, $scope, $storage, $nodeCallback, ExpressionContext::createDeep($context->shouldResolveTemplateArguments()));
4654
$nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $entryScope, $storage);
47-
$conditionType = ($nodeScopeResolver->shouldTreatPhpDocTypesAsCertain() ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
55+
$conditionType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
4856
$ifAlwaysTrue = $conditionType->isTrue()->yes();
4957
$exitPoints = [];
5058
$throwPoints = $condResult->getThrowPoints();
@@ -79,7 +87,7 @@ public function processStmt(
7987
foreach ($stmt->elseifs as $elseif) {
8088
$condResult = $nodeScopeResolver->processExprNode($stmt, $elseif->cond, $condScope, $storage, $nodeCallback, ExpressionContext::createDeep($context->shouldResolveTemplateArguments()));
8189
$nodeScopeResolver->callNodeCallback($nodeCallback, $elseif, $scope, $storage);
82-
$elseIfConditionType = ($nodeScopeResolver->shouldTreatPhpDocTypesAsCertain() ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
90+
$elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
8391
$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints());
8492
$impurePoints = array_merge($impurePoints, $condResult->getImpurePoints());
8593
$branchScopeStatementResult = $nodeScopeResolver->processStmtNodesInternal($elseif, $elseif->stmts, $condResult->getTruthyScope(), $storage, $nodeCallback, $context);

‎src/Analyser/StmtHandler/WhileHandler.php‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use PHPStan\Analyser\StmtHandler;
1818
use PHPStan\Analyser\TypeSpecifierContext;
1919
use PHPStan\Analyser\VariableFlow;
20+
use PHPStan\DependencyInjection\AutowiredParameter;
2021
use PHPStan\DependencyInjection\AutowiredService;
2122
use PHPStan\Node\BreaklessWhileLoopNode;
2223
use function array_merge;
@@ -29,6 +30,15 @@
2930
final class WhileHandler implements StmtHandler
3031
{
3132

33+
public function __construct(
34+
#[AutowiredParameter]
35+
private bool $polluteScopeWithLoopInitialAssignments,
36+
#[AutowiredParameter]
37+
private bool $treatPhpDocTypesAsCertain,
38+
)
39+
{
40+
}
41+
3242
public function supports(Stmt $stmt): bool
3343
{
3444
return $stmt instanceof While_;
@@ -51,10 +61,10 @@ public function processStmt(
5161
$scope->pushExpressionResultStorage($storage);
5262
try {
5363
$condResult = $nodeScopeResolver->processExprNode($stmt, $stmt->cond, $scope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep(resolveTemplateArguments: false));
54-
$beforeCondBooleanType = ($nodeScopeResolver->shouldTreatPhpDocTypesAsCertain() ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
64+
$beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean();
5565
$condScope = $condResult->getFalseyScope();
5666
if (!$context->isTopLevel() && $beforeCondBooleanType->isFalse()->yes()) {
57-
if (!$nodeScopeResolver->shouldPolluteScopeWithLoopInitialAssignments()) {
67+
if (!$this->polluteScopeWithLoopInitialAssignments) {
5868
$scope = $condScope->mergeWith($scope);
5969
}
6070

@@ -173,7 +183,7 @@ public function processStmt(
173183
$alwaysIterates = false;
174184
$neverIterates = false;
175185
if ($context->isTopLevel()) {
176-
$condBooleanType = ($nodeScopeResolver->shouldTreatPhpDocTypesAsCertain() ? $bodyCondResult->getType() : $bodyCondResult->getNativeType())->toBoolean();
186+
$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyCondResult->getType() : $bodyCondResult->getNativeType())->toBoolean();
177187
$alwaysIterates = $condBooleanType->isTrue()->yes();
178188
$neverIterates = $condBooleanType->isFalse()->yes();
179189
}
@@ -200,7 +210,7 @@ public function processStmt(
200210
$finalScope = null;
201211
}
202212
if (!$isIterableAtLeastOnce) {
203-
if (!$nodeScopeResolver->shouldPolluteScopeWithLoopInitialAssignments()) {
213+
if (!$this->polluteScopeWithLoopInitialAssignments) {
204214
$condScope = $condScope->mergeWith($scope);
205215
}
206216
$finalScope = $finalScope === null ? $condScope : $finalScope->mergeWith($condScope);

0 commit comments

Comments
 (0)