Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
68e4440
Remove variable writes that are never read
ondrejmirtes Sep 10, 2026
6cae955
Remove unused private-method parameters
ondrejmirtes Sep 10, 2026
fd4a172
Remove assignments of values the variables already have
ondrejmirtes Sep 10, 2026
471b8fd
Use the assigned variables in E2E fixtures
ondrejmirtes Sep 10, 2026
69e0a94
Use the assigned variables in levels test fixtures
ondrejmirtes Sep 10, 2026
9359d69
Normalize paths in NodeScopeResolver::setAnalysedFiles()
ondrejmirtes Sep 10, 2026
b1c223b
Detect unused variables and parameters with immutable handler results
ondrejmirtes Sep 10, 2026
6a8637a
Bump expected turbo version
ondrejmirtes Sep 10, 2026
b1ba6f8
Cover variable reads in nullsafe call arguments
ondrejmirtes Sep 10, 2026
435856a
Add regression tests for #12789, #13472 and #14258
ondrejmirtes Sep 10, 2026
c1bc35d
Add regression test for #12012
ondrejmirtes Sep 10, 2026
fb7a088
Add regression test for #11483
ondrejmirtes Sep 10, 2026
5645d3d
Add regression test for #10202
ondrejmirtes Sep 10, 2026
69b9979
Cover PHP-version-dependent unused catch variables
ondrejmirtes Sep 10, 2026
bcb75f5
Do not report writes to a variable returned by reference
ondrejmirtes Sep 10, 2026
bbed891
Cover unused writes in statically dead branches
ondrejmirtes Sep 10, 2026
3723cc1
Treat comment-only bodies as no-op stubs in the unused-parameter rules
ondrejmirtes Sep 10, 2026
842976e
Cover array-shift loop inference and statically unreachable assignments
ondrejmirtes Sep 10, 2026
8a3b098
Treat func_get_arg() like func_get_args() when observing parameters
ondrejmirtes Sep 10, 2026
8c268f0
Cover variable liveness across evaluation order and control flow
ondrejmirtes Sep 10, 2026
7fdb62a
Cover variable liveness when foreach scope pollution is disabled
ondrejmirtes Sep 10, 2026
163f9a8
Cover variable usage through globals, static initializers and nested …
ondrejmirtes Sep 10, 2026
610b854
Respect short-circuiting of nullsafe dynamic member names
ondrejmirtes Sep 10, 2026
695d68f
Reuse concrete return types across inference contexts
ondrejmirtes Sep 10, 2026
bf91dae
Keep variables read by fallback Throwable catches live
ondrejmirtes Sep 10, 2026
a33871d
Track types written through variable variables
ondrejmirtes Sep 10, 2026
3f12a51
Preserve array writes made while evaluating an offset assignment
ondrejmirtes Sep 10, 2026
e5e374f
Retain variable mentions in skipped catch bodies
ondrejmirtes Sep 10, 2026
b84e05c
Observe writes through arrow function reference parameters
ondrejmirtes Sep 10, 2026
7d2352b
Include implicit throw points when analysing broad catches
ondrejmirtes Sep 10, 2026
d9ad990
Put the arrow reference fixture PHP version constraint on its first line
ondrejmirtes Sep 10, 2026
a83b91f
Mark reflection parameter checks and type stringification as non-thro…
ondrejmirtes Sep 10, 2026
5195842
Preserve reflection parameter query return types on PHP 7.4
ondrejmirtes Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ parameters:
checkImportedClassNameCase: true
sortWithoutEffect: true
unresolvedTemplateArguments: true
unusedVariable: true
unusedParameters: true
15 changes: 15 additions & 0 deletions conf/config.level4.neon
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ conditionalTags:
phpstan.rules.rule: %featureToggles.finiteTypesInHaystack%
PHPStan\Rules\Comparison\SwitchConditionRule:
phpstan.rules.rule: %featureToggles.switchConditionAlwaysFalse%
PHPStan\Rules\DeadCode\UnusedVariableRule:
phpstan.rules.rule: %featureToggles.unusedVariable%
PHPStan\Rules\Functions\UnusedFunctionParametersRule:
phpstan.rules.rule: %featureToggles.unusedParameters%
PHPStan\Rules\Methods\UnusedMethodParametersRule:
phpstan.rules.rule: %featureToggles.unusedParameters%

parameters:
checkAdvancedIsset: true
Expand Down Expand Up @@ -49,3 +55,12 @@ services:
class: PHPStan\Rules\Comparison\SwitchConditionRule
arguments:
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%

-
class: PHPStan\Rules\DeadCode\UnusedVariableRule

-
class: PHPStan\Rules\Functions\UnusedFunctionParametersRule

-
class: PHPStan\Rules\Methods\UnusedMethodParametersRule
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ parameters:
checkImportedClassNameCase: false
sortWithoutEffect: false
unresolvedTemplateArguments: false
unusedVariable: false
unusedParameters: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down Expand Up @@ -154,6 +156,7 @@ parameters:
- ../stubs/ReflectionMethod.stub
- ../stubs/ReflectionParameter.stub
- ../stubs/ReflectionProperty.stub
- ../stubs/ReflectionType.stub
- ../stubs/iterable.stub
- ../stubs/ArrayObject.stub
- ../stubs/WeakReference.stub
Expand Down
2 changes: 2 additions & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ parametersSchema:
checkImportedClassNameCase: bool()
sortWithoutEffect: bool()
unresolvedTemplateArguments: bool()
unusedVariable: bool()
unusedParameters: bool()
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
8 changes: 4 additions & 4 deletions e2e/ignore-error-extension/phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
parameters:
ignoreErrors:
-
message: '#^This is an error from a rule that uses a collector$#'
message: '#^This is an error from a rule that uses a collector\: ClassCollector$#'
identifier: class.name
count: 1
path: src/ClassCollector.php

-
message: '#^This is an error from a rule that uses a collector$#'
message: '#^This is an error from a rule that uses a collector\: ClassRule$#'
identifier: class.name
count: 1
path: src/ClassRule.php

-
message: '#^This is an error from a rule that uses a collector$#'
message: '#^This is an error from a rule that uses a collector\: ControllerActionReturnTypeIgnoreExtension$#'
identifier: class.name
count: 1
path: src/ControllerActionReturnTypeIgnoreExtension.php

-
message: '#^This is an error from a rule that uses a collector$#'
message: '#^This is an error from a rule that uses a collector\: ControllerClassNameIgnoreExtension$#'
identifier: class.name
count: 1
path: src/ControllerClassNameIgnoreExtension.php
Expand Down
2 changes: 1 addition & 1 deletion e2e/ignore-error-extension/src/ClassRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function processNode(Node $node, Scope $scope) : array

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')
$errors[] = RuleErrorBuilder::message(sprintf('This is an error from a rule that uses a collector: %s', $className))
->file($file)
->line($line)
->identifier('class.name')
Expand Down
4 changes: 3 additions & 1 deletion e2e/trait-caching/data/TestClassUsingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function doBar()
return $this->doFoo();
}

public function doBaz(): void
public function doBaz(): \stdClass
{
$class = new class() {

Expand All @@ -29,6 +29,8 @@ public function doBar()
return $this->doFoo();
}
};

return $class->doBar();
}

}
7 changes: 7 additions & 0 deletions src/Analyser/ArgsResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ final class ArgsResult

/**
* @param array<int, ExpressionResult> $argResults keyed by spl_object_id of each argument's value expression
* @param array<int, true> $byRefArguments
*/
public function __construct(
private ExpressionResult $expressionResult,
private ?ParametersAcceptor $resolvedParametersAcceptor,
private array $argResults,
private array $byRefArguments = [],
)
{
}
Expand Down Expand Up @@ -72,6 +74,11 @@ public function requireArgResult(Expr $argValue): ExpressionResult
return $result;
}

public function isPassedByReference(Expr $arg): bool
{
return isset($this->byRefArguments[spl_object_id($arg)]);
}

public function getScope(): MutatingScope
{
return $this->expressionResult->getScope();
Expand Down
4 changes: 4 additions & 0 deletions src/Analyser/ExprHandler/ArrayDimFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Analyser\VariableFlow;
use PHPStan\Analyser\VariableFlowBuilder;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\Expr\TypeExpr;
use PHPStan\Reflection\ParametersAcceptorSelector;
Expand Down Expand Up @@ -84,6 +86,7 @@ public function composeResult(NodeScopeResolver $nodeScopeResolver, Stmt $stmt,
$scope,
beforeScope: $beforeScope,
expr: $expr,
variableFlow: $varResult->getVariableFlow(),
hasYield: $varResult->hasYield(),
isAlwaysTerminating: $varResult->isAlwaysTerminating(),
throwPoints: $varResult->getThrowPoints(),
Expand Down Expand Up @@ -118,6 +121,7 @@ public function composeResult(NodeScopeResolver $nodeScopeResolver, Stmt $stmt,
$scope,
beforeScope: $beforeScope,
expr: $expr,
variableFlow: VariableFlow::sequence($varResult->getVariableFlow(), $dimResult->getVariableFlow(), VariableFlowBuilder::throws($expr, $throwPoints)),
hasYield: $dimResult->hasYield() || $varResult->hasYield(),
isAlwaysTerminating: $dimResult->isAlwaysTerminating() || $varResult->isAlwaysTerminating(),
throwPoints: $throwPoints,
Expand Down
9 changes: 9 additions & 0 deletions src/Analyser/ExprHandler/ArrayHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\VariableFlow;
use PHPStan\Analyser\VariableFlowBuilder;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\LiteralArrayItem;
use PHPStan\Node\LiteralArrayNode;
Expand Down Expand Up @@ -53,6 +55,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$beforeScope = $scope;
$itemNodes = [];
$itemResults = [];
$variableFlows = [];
$hasYield = false;
$throwPoints = [];
$impurePoints = [];
Expand All @@ -63,6 +66,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
if ($arrayItem->key !== null) {
$keyResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->key, $scope, $storage, $nodeCallback, $context->enterDeep());
$itemResults[spl_object_id($arrayItem->key)] = $keyResult;
$variableFlows[] = $keyResult->getVariableFlow();
$hasYield = $hasYield || $keyResult->hasYield();
$throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints());
Expand All @@ -72,6 +76,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex

$valueResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->value, $scope, $storage, $nodeCallback, $context->enterDeep());
$itemResults[spl_object_id($arrayItem->value)] = $valueResult;
$variableFlows[] = $valueResult->getVariableFlow();
if ($arrayItem->byRef) {
$variableFlows[] = VariableFlowBuilder::escapeRoot($arrayItem->value);
}
$hasYield = $hasYield || $valueResult->hasYield();
$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints());
Expand All @@ -88,6 +96,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope,
beforeScope: $beforeScope,
expr: $expr,
variableFlow: VariableFlow::sequence(...$variableFlows),
hasYield: $hasYield,
isAlwaysTerminating: $isAlwaysTerminating,
throwPoints: $throwPoints,
Expand Down
16 changes: 16 additions & 0 deletions src/Analyser/ExprHandler/ArrowFunctionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Analyser\VariableFlow;
use PHPStan\DependencyInjection\AutowiredService;
use function is_string;

/**
* @implements ExprHandler<ArrowFunction>
Expand Down Expand Up @@ -77,6 +79,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$result->getScope(),
beforeScope: $scope,
expr: $expr,
variableFlow: $result->getVariableFlow(),
hasYield: $result->hasYield(),
isAlwaysTerminating: false,
throwPoints: [],
Expand All @@ -88,4 +91,17 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
);
}

public static function getVariableFlow(ArrowFunction $expr, ExpressionResult $bodyResult): VariableFlow
{
$outputs = [];
foreach ($expr->params as $param) {
if (!$param->byRef || !$param->var instanceof Expr\Variable || !is_string($param->var->name)) {
continue;
}
$outputs[] = VariableFlow::read($param->var->name);
}

return VariableFlow::arrow($expr, $bodyResult->getVariableFlow(), VariableFlow::sequence(...$outputs));
}

}
Loading
Loading