Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3a9b598
Accept precomputed operand types in RicherScopeGetTypeHelper
ondrejmirtes Aug 14, 2026
3832b5e
Let ExpressionResult own its expression's type and narrowing
ondrejmirtes Aug 14, 2026
6073452
Store whole ExpressionResults instead of before-scopes
ondrejmirtes Aug 14, 2026
0e0943a
Compose default narrowing from walk results in DefaultNarrowingHelper
ondrejmirtes Aug 14, 2026
0501aa7
Replace EqualityTypeSpecifyingHelper with result-composed IdenticalNa…
ondrejmirtes Aug 14, 2026
450a427
Compose boolean narrowing from operand results
ondrejmirtes Aug 14, 2026
51288b7
Fold leaf handler type resolution into result callbacks
ondrejmirtes Aug 14, 2026
a9c60e5
Fabricate virtual expression results equal to walked ones
ondrejmirtes Aug 14, 2026
d1adbe9
Rework the call handlers around preliminary results and ArgsResult
ondrejmirtes Aug 14, 2026
41f0f57
Read impossible-check verdicts from the call's own result
ondrejmirtes Aug 14, 2026
7170e82
Inline nullsafe short-circuiting and compose receiver narrowing
ondrejmirtes Aug 14, 2026
9ed573b
Fetch handlers read operand results
ondrejmirtes Aug 14, 2026
e860e02
Resolve isset, empty and coalesce from chain results
ondrejmirtes Aug 14, 2026
7ac347b
Decompose ternary and match through composed narrowing
ondrejmirtes Aug 14, 2026
017be6d
Build closure types eagerly from the single body walk
ondrejmirtes Aug 14, 2026
b5261e3
Thread assigned-value results through the assignment handlers
ondrejmirtes Aug 14, 2026
da3de2f
Answer type and narrowing questions from stored ExpressionResults
ondrejmirtes Aug 14, 2026
f33d64e
Drop resolveType() and specifyTypes() from ExprHandler
ondrejmirtes Aug 14, 2026
911bfeb
Short-circuit ScopeOps scans over conditional expressions
ondrejmirtes Aug 14, 2026
613afb6
Bump expected turbo version
ondrejmirtes Aug 14, 2026
2728efb
Emit node callbacks after the node's results are stored
ondrejmirtes Aug 14, 2026
217f2f3
Emit condition-statement and assign-target callbacks after their resu…
ondrejmirtes Aug 14, 2026
efc936e
Store boolean results before their virtual nodes and defer the foreac…
ondrejmirtes Aug 14, 2026
4703c46
Trim per-store and per-gatherer overhead in the fiber resolver
ondrejmirtes Aug 14, 2026
5ee3a17
Prime the storage for constructor return-type and type-specifying ext…
ondrejmirtes Aug 14, 2026
48858c3
Sum the output-buffer level without walking a synthetic node
ondrejmirtes Aug 14, 2026
23adeb8
Compose static-call and clone-with types without synthetic-node walks
ondrejmirtes Aug 14, 2026
aa49c32
Read $this from scope state and document the parent-instantiation walk
ondrejmirtes Aug 14, 2026
d3dec69
Memoize function and constant name resolution in BetterReflectionProv…
ondrejmirtes Aug 15, 2026
34244b2
Restore the non-type-driven fast path for the resolved acceptor
ondrejmirtes Aug 15, 2026
cb69f5e
Degrade on-demand walks of handler-less nodes to mixed
ondrejmirtes Aug 15, 2026
0d9fa98
Guard FiberScope stored-result reads with the asking scope's variable…
ondrejmirtes Aug 15, 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
42 changes: 9 additions & 33 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ parameters:
count: 3
path: src/Analyser/ExprHandler/AssignHandler.php

-
rawMessage: Casting to string something that's already string.
identifier: cast.useless
count: 3
path: src/Analyser/ExprHandler/Helper/ConditionalExpressionHolderHelper.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
identifier: phpstanApi.instanceofType
Expand All @@ -36,40 +42,22 @@ parameters:
count: 2
path: src/Analyser/ExprHandler/BinaryOpHandler.php

-
rawMessage: '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/Analyser/ExprHandler/BooleanNotHandler.php

-
rawMessage: '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/ExprHandler/Helper/EqualityTypeSpecifyingHelper.php

-
rawMessage: '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/ExprHandler/Helper/EqualityTypeSpecifyingHelper.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
identifier: phpstanApi.instanceofType
count: 2
path: src/Analyser/ExprHandler/IssetHandler.php
path: src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php

-
rawMessage: 'Only numeric types are allowed in pre-increment, float|int|string|null given.'
identifier: preInc.nonNumeric
count: 1
path: src/Analyser/ExprHandler/PreIncHandler.php
path: src/Analyser/ExprHandler/Helper/IncDecTypeHelper.php

-
rawMessage: Casting to string something that's already string.
identifier: cast.useless
count: 1
count: 4
path: src/Analyser/MutatingScope.php

-
Expand Down Expand Up @@ -114,18 +102,6 @@ parameters:
count: 1
path: src/Analyser/RuleErrorTransformer.php

-
rawMessage: Casting to string something that's already string.
identifier: cast.useless
count: 2
path: src/Analyser/ScopeOps.php

-
rawMessage: '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/Analyser/TypeSpecifier.php

-
rawMessage: 'Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\Collectors\Collector::processNode().'
identifier: generics.variance
Expand Down
19 changes: 17 additions & 2 deletions src/Analyser/ArgsResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,41 @@

namespace PHPStan\Analyser;

use PhpParser\Node\Expr;
use PHPStan\Reflection\ParametersAcceptor;
use function spl_object_id;

/**
* Result of NodeScopeResolver::processArgs(): the scope/throw/impure state after
* processing all arguments (wrapped ExpressionResult) plus the ParametersAcceptor
* resolved from the arg types gathered on the arg-to-arg evolving scope. The
* resolved acceptor is type-driven (selectFromTypes) so its generics are resolved
* against the actual argument types - callers wire it into the call's return
* type. Null when the call had no variants (dynamic callee).
* against the actual argument types - callers wire it into the call expression's
* stored return type. Null when the call had no variants (dynamic callee).
*/
final class ArgsResult
{

/**
* @param array<int, ExpressionResult> $argResults keyed by spl_object_id of each argument's value expression
*/
public function __construct(
private ExpressionResult $expressionResult,
private ?ParametersAcceptor $resolvedParametersAcceptor,
private array $argResults = [],
)
{
}

/**
* The already-processed ExpressionResult of a call argument's value expression,
* so callers read its type via the result instead of re-asking the scope.
*/
public function getArgResult(Expr $argValue): ?ExpressionResult
{
return $this->argResults[spl_object_id($argValue)] ?? null;
}

public function getScope(): MutatingScope
{
return $this->expressionResult->getScope();
Expand Down
4 changes: 2 additions & 2 deletions src/Analyser/ConditionalExpressionHolderRecipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function evaluate(MutatingScope $scope): array
// dropped-self-condition complement below
$conditionOriginalTypes = [];
foreach ($this->conditionEntries as [$exprString, $expr, $fromSureTypes, $type]) {
$scopeType = $scope->getType($expr);
$scopeType = $scope->getStateType($expr);
$conditionType = $fromSureTypes
? TypeCombinator::remove($scopeType, $type)
: TypeCombinator::intersect($scopeType, $type);
Expand Down Expand Up @@ -81,7 +81,7 @@ public function evaluate(MutatingScope $scope): array
continue;
}

$targetType = $pinnedTargetType ?? $scope->getType($expr);
$targetType = $pinnedTargetType ?? $scope->getStateType($expr);
$holderType = $this->holdersFromSureTypes
? TypeCombinator::intersect($targetType, $type)
: TypeCombinator::remove($targetType, $type);
Expand Down
30 changes: 13 additions & 17 deletions src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
final class DirectInternalScopeFactory implements InternalScopeFactory
{

private ExpressionResultStorageStack $expressionResultStorageStack;

/**
* @param int|array{min: int, max: int}|null $configPhpVersion
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
Expand All @@ -40,8 +42,10 @@ public function __construct(
private $nodeCallback,
private ConstantResolver $constantResolver,
private bool $fiber = false,
?ExpressionResultStorageStack $expressionResultStorageStack = null,
)
{
$this->expressionResultStorageStack = $expressionResultStorageStack ?? new ExpressionResultStorageStack();
}

public function create(
Expand Down Expand Up @@ -79,6 +83,7 @@ public function create(
$this->propertyReflectionFinder,
$this->parser,
$this->constantResolver,
$this->expressionResultStorageStack,
$context,
$this->phpVersion,
$this->attributeReflectionFactory,
Expand All @@ -104,25 +109,15 @@ public function create(

public function toFiberFactory(): InternalScopeFactory
{
return new self(
$this->container,
$this->reflectionProvider,
$this->initializerExprTypeResolver,
$this->expressionTypeResolverExtensions,
$this->exprPrinter,
$this->typeSpecifier,
$this->propertyReflectionFinder,
$this->parser,
$this->phpVersion,
$this->attributeReflectionFactory,
$this->configPhpVersion,
$this->nodeCallback,
$this->constantResolver,
true,
);
return $this->withFlavor(true);
}

public function toMutatingFactory(): InternalScopeFactory
{
return $this->withFlavor(false);
}

private function withFlavor(bool $fiber): self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this read withFiber?

{
return new self(
$this->container,
Expand All @@ -138,7 +133,8 @@ public function toMutatingFactory(): InternalScopeFactory
$this->configPhpVersion,
$this->nodeCallback,
$this->constantResolver,
false,
$fiber,
$this->expressionResultStorageStack,
);
}

Expand Down
8 changes: 5 additions & 3 deletions src/Analyser/DisjunctionBranchUnionAugment.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Analyser;

use PhpParser\Node\Expr;
use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
Expand All @@ -23,7 +24,8 @@ final class DisjunctionBranchUnionAugment implements DeferredSpecifiedTypesAugme
* @param list<array{Expr, Type, Type}> $candidates [target expr, left branch type, right branch type]
*/
public function __construct(
private TypeSpecifier $typeSpecifier,
private NodeScopeResolver $nodeScopeResolver,
private DefaultNarrowingHelper $defaultNarrowingHelper,
private array $candidates,
)
{
Expand All @@ -38,7 +40,7 @@ public function evaluate(MutatingScope $scope): ?SpecifiedTypes
}

// the guard above pins the target as tracked on the applying scope
$originalType = $scope->getType($targetExpr);
$originalType = $this->nodeScopeResolver->readScopeStateOrSyntheticType($targetExpr, $scope);
// re-pinning eagerly priced branch forms of a template-typed subject
// stacks the template inside its own bound (`T of T of ...` - the
// pin intersects with the declared template); its narrowing already
Expand All @@ -62,7 +64,7 @@ public function evaluate(MutatingScope $scope): ?SpecifiedTypes
continue;
}

$created = $this->typeSpecifier->create($targetExpr, $unionType, TypeSpecifierContext::createTrue(), $scope);
$created = $this->defaultNarrowingHelper->createForSubject($targetExpr, $unionType, TypeSpecifierContext::createTrue(), $scope);
$result = $result === null ? $created : $result->unionWith($created);
}

Expand Down
23 changes: 15 additions & 8 deletions src/Analyser/DisjunctionHolderProjectionAugment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Analyser;

use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper;
use PHPStan\Type\TypeCombinator;
use function array_key_first;

Expand Down Expand Up @@ -29,7 +30,8 @@ final class DisjunctionHolderProjectionAugment implements DeferredSpecifiedTypes
* be added on top
*/
public function __construct(
private TypeSpecifier $typeSpecifier,
private NodeScopeResolver $nodeScopeResolver,
private DefaultNarrowingHelper $defaultNarrowingHelper,
private $leftTruthyScope,
private MutatingScope $leftFalseyScope,
private $rightTruthyScope,
Expand Down Expand Up @@ -69,19 +71,24 @@ public function evaluate(MutatingScope $scope): ?SpecifiedTypes
}
$leftTruthyScope ??= ($this->leftTruthyScope)();
$rightTruthyScope ??= ($this->rightTruthyScope)();
if (!$leftTruthyScope->hasExpressionType($targetExpr)->yes()) {
continue;
}
if (!$rightTruthyScope->hasExpressionType($targetExpr)->yes()) {
continue;
}

// the guard above pins the target as tracked on the applying
// scope; the branch scopes are its own filtered views, so their
// reads answer from state (or price the same tracked state)
$origType = $scope->getType($targetExpr);
// the guards above pin the target as tracked on all three scopes -
// scope state answers without a walk
$origType = $this->nodeScopeResolver->readScopeStateOrSyntheticType($targetExpr, $scope);

$leftType = $leftTruthyScope->getType($targetExpr);
$leftType = $this->nodeScopeResolver->readScopeStateOrSyntheticType($targetExpr, $leftTruthyScope);
$leftNarrowed = !$leftType->equals($origType) && $origType->isSuperTypeOf($leftType)->yes();
if (!$leftNarrowed) {
continue;
}

$rightType = $rightTruthyScope->getType($targetExpr);
$rightType = $this->nodeScopeResolver->readScopeStateOrSyntheticType($targetExpr, $rightTruthyScope);
$rightNarrowed = !$rightType->equals($origType) && $origType->isSuperTypeOf($rightType)->yes();
if (!$rightNarrowed) {
continue;
Expand All @@ -92,7 +99,7 @@ public function evaluate(MutatingScope $scope): ?SpecifiedTypes
continue;
}

$created = $this->typeSpecifier->create($targetExpr, $unionType, TypeSpecifierContext::createTrue(), $scope);
$created = $this->defaultNarrowingHelper->createSubjectTypes($scope, $targetExpr, null, $unionType, TypeSpecifierContext::createTrue());
$result = $result === null ? $created : $result->unionWith($created);
}
}
Expand Down
16 changes: 0 additions & 16 deletions src/Analyser/ExprHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PHPStan\DependencyInjection\ExtensionInterface;
use PHPStan\Type\Type;

/**
* @template T of Expr
Expand Down Expand Up @@ -34,19 +33,4 @@ public function processExpr(
ExpressionContext $context,
): ExpressionResult;

/**
* @param T $expr
*/
public function resolveType(MutatingScope $scope, Expr $expr): Type;

/**
* @param T $expr
*/
public function specifyTypes(
TypeSpecifier $typeSpecifier,
Scope $scope,
Expr $expr,
TypeSpecifierContext $context,
): SpecifiedTypes;

}
Loading
Loading