Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
f9d803e
Introduce ExpressionResultFactory
ondrejmirtes Jun 11, 2026
f273819
ExpressionResult - add beforeScope
ondrejmirtes Jun 11, 2026
59996fe
ExpressionResult - add Expr
ondrejmirtes Jun 11, 2026
21bf8e9
Store ExpressionResult instead of before-Scope
ondrejmirtes Jun 11, 2026
8ae0839
Fill the missing gaps in expr processing
ondrejmirtes Jun 12, 2026
83535c9
Divide ExprHandler into TypeResolvingExprHandler
ondrejmirtes Jun 12, 2026
30424b4
ScalarHandler stops implementing TypeResolvingExprHandler
ondrejmirtes Jun 12, 2026
5c4ea04
ExpressionResultStorageStack - answer type questions from ExpressionR…
ondrejmirtes Jun 12, 2026
55f3c7b
Migrate ArrayHandler - per-item types from ExpressionResults
ondrejmirtes Jun 12, 2026
5664d64
Throw on unbalanced ExpressionResultStorageStack pop
ondrejmirtes Jun 12, 2026
538dd88
Fix PHP 7.4 compat
ondrejmirtes Jun 12, 2026
9f40fe1
Migrate VariableHandler and InstanceofHandler - narrowing from Expres…
ondrejmirtes Jun 12, 2026
9179b49
Add regression tests for evaluation-point array item types
ondrejmirtes Jun 12, 2026
2515d8b
Add regression test for certainty of undefined variables in loops
ondrejmirtes Jun 12, 2026
43b6fa5
Store expressions even without FNSR
ondrejmirtes Jun 12, 2026
12ca8df
Only sureNot specifications skip certainly-undefined variables
ondrejmirtes Jun 12, 2026
9d8fefa
This is better
ondrejmirtes Jun 12, 2026
e7621fa
ExpressionResult::createTypesCallback - the inside-out TypeSpecifier:…
ondrejmirtes Jun 12, 2026
cbd9441
Coalesce, Ternary, BooleanAnd, BooleanOr stop implementing TypeResolv…
ondrejmirtes Jun 12, 2026
b3197c9
Add regression test for conditional holders narrowing coalesce of pro…
ondrejmirtes Jun 12, 2026
7ebb050
Never process expressions on a FiberScope
ondrejmirtes Jun 12, 2026
b48a9c5
Convert rule-facing FiberScope at the new-world hook boundary
ondrejmirtes Jun 12, 2026
5fdb451
Guard that only synthetic nodes reach the pending-fiber on-demand path
ondrejmirtes Jun 13, 2026
eb81e63
Fix immediately-invoked-closure fiber flush; make the new-world guard…
ondrejmirtes Jun 13, 2026
eaeba8f
Flush pending fibers only at scope boundaries; process dropped call args
ondrejmirtes Jun 13, 2026
92cc156
Add PHPSTAN_GUARD_NW guard: no getType on a real node before processE…
ondrejmirtes Jun 13, 2026
8759198
BitwiseNotHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
5b01972
One more should-not
ondrejmirtes Jun 16, 2026
3bfef7c
UnaryMinusHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
a9fc38f
UnaryPlusHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
e2843b6
ConstFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
7838476
PrintHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
23e7cdd
ThrowHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
65b57ce
ExitHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
21ebb5e
EvalHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
4f90ae7
IncludeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
90e32ad
YieldFromHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
1a9a69a
ClassConstFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
c2e0806
InterpolatedStringHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
f28c52f
CloneHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
af23a79
YieldHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
0c78464
AlwaysRememberedExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
225cc29
NativeTypeExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
0e4cb92
FunctionCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
97f60ed
MethodCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
acf2185
StaticMethodCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
9bb2c01
InstantiationCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
140482b
TypeExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
431f847
Fix failure of forwarding ExpressionResult
ondrejmirtes Jun 17, 2026
09065ca
ErrorSuppressHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
ee55581
CastHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
d40d377
CastStringHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
f77c8de
PostIncHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
1fad852
PostDecHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
f92af63
PipeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
bf43d04
PreIncHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
a9950bf
PreDecHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
873cd34
AssignOpHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
bfaea31
AssignHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
8bbf880
Track containsNullsafe on ExpressionResult and propagate it through f…
ondrejmirtes Jun 17, 2026
c017942
PropertyFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
9a89588
StaticPropertyFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
3e4e367
Store ArrayDimFetch assign-target results with a typeCallback
ondrejmirtes Jun 17, 2026
d4fdf57
ArrayDimFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
3ea7cdd
Introduce IssetabilityDescriptor and fold it in MutatingScope::issetC…
ondrejmirtes Jun 17, 2026
a8cf306
Move issetCheck onto ExpressionResult with isset()/empty() convenience
ondrejmirtes Jun 17, 2026
38995a2
CoalesceHandler reads issetCheck from the left result instead of the …
ondrejmirtes Jun 17, 2026
4c46aa4
EmptyHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
7b09a80
Match issetCheckUndefined ordering in IssetabilityDescriptor::checkUn…
ondrejmirtes Jun 17, 2026
ba726dd
Re-evaluate getCurrentTypesOfSpecifiedExpr on the asking scope
ondrejmirtes Jun 17, 2026
2bad7b0
Rules\IssetCheck folds IssetabilityDescriptor instead of re-walking t…
ondrejmirtes Jun 17, 2026
d339180
MatchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
4c784db
NullsafePropertyFetchHandler and NullsafeMethodCallHandler are no lon…
ondrejmirtes Jun 17, 2026
31888e7
Move first-class callable type resolution into the *CallableNode hand…
ondrejmirtes Jun 17, 2026
f3371dc
Price synthetic narrowing expressions on demand in getCurrentTypesOfS…
ondrejmirtes Jun 17, 2026
fad3c7b
BinaryOpHandler and BooleanNotHandler are no longer TypeResolvingExpr…
ondrejmirtes Jun 17, 2026
7ac47a9
Fix getIssetabilityDescriptor shadowed by descriptor-less assignment-…
ondrejmirtes Jun 17, 2026
00ca7a5
Eliminate the OriginalPropertyTypeExpr virtual node
ondrejmirtes Jun 18, 2026
d82266b
Eliminate the GetOffsetValueTypeExpr virtual node
ondrejmirtes Jun 18, 2026
4b95a42
Eliminate the GetIterableKeyTypeExpr virtual node
ondrejmirtes Jun 18, 2026
254d9ff
Eliminate the GetIterableValueTypeExpr virtual node
ondrejmirtes Jun 18, 2026
0e0c238
ExistingArrayDimFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 18, 2026
a877de7
UnsetOffsetExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 18, 2026
d869c72
SetOffsetValueTypeExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 18, 2026
9a6fadc
SetExistingOffsetValueTypeExprHandler is no longer TypeResolvingExprH…
ondrejmirtes Jun 18, 2026
6e617eb
Read operand types from ExpressionResults in Throw/BooleanAnd/Coalesc…
ondrejmirtes Jun 18, 2026
2f80158
Read operand types from ExpressionResults in Ternary/ArrayDimFetch/Pr…
ondrejmirtes Jun 18, 2026
6963841
Read sub-expression types from ExpressionResults in offset Virtual ha…
ondrejmirtes Jun 18, 2026
11c97c0
Read child/narrowed types from results instead of Scope::getType in l…
ondrejmirtes Jun 18, 2026
d2e2cde
Process synthetic offsetGet/callable nodes in processExpr, read resul…
ondrejmirtes Jun 18, 2026
b043f84
Add readStoredOrPriceOnDemand/priceSyntheticOnDemand and use them ins…
ondrejmirtes Jun 18, 2026
414974e
Price synthetic unary-minus operand on demand instead of Scope::getType
ondrejmirtes Jun 18, 2026
66c8df2
Read child/synthetic types via result or helpers in Match/BinaryOp/Eq…
ondrejmirtes Jun 18, 2026
d422c46
Thread NodeScopeResolver into narrowing/throw-point helpers to avoid …
ondrejmirtes Jun 18, 2026
214869a
Read child/synthetic types via results or helpers in AssignHandler's …
ondrejmirtes Jun 18, 2026
46a53f8
Read expr types via results or helpers in NodeScopeResolver instead o…
ondrejmirtes Jun 18, 2026
bcd0e02
Correct the explanation of the two load-bearing Scope::getType() exce…
ondrejmirtes Jun 18, 2026
53e4fa3
Read Identical operand types from results in RicherScopeGetTypeHelper…
ondrejmirtes Jun 18, 2026
259d4c1
Read expr types from results in SpecifiedTypes::normalize when called…
ondrejmirtes Jun 18, 2026
3dd027b
Read assign-target sub-expression types from their results instead of…
ondrejmirtes Jun 18, 2026
9f0a479
Read while-loop condition type from its result instead of the on-dema…
ondrejmirtes Jun 18, 2026
376ea07
Pass already-computed results into ImplicitToStringCallHelper and rea…
ondrejmirtes Jun 18, 2026
5a1230a
NSRT test for precise Scope
ondrejmirtes Jun 18, 2026
0b14737
Extract intrinsic argument parameter overrides from selectFromArgs in…
ondrejmirtes Jun 18, 2026
bc198cf
Resolve argument types on an arg-to-arg evolving scope and select the…
ondrejmirtes Jun 19, 2026
d064584
FuncCallHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
7f2b9fc
MethodCallHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
5e03760
NewHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
b1f5af5
StaticCallHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
7257b5b
Extract combineVariantsForNormalization into a shared ParametersAccep…
ondrejmirtes Jun 19, 2026
4c1ffb2
Add regression test for #13253
ondrejmirtes Jun 19, 2026
0d85dce
Add regression test for #14396
ondrejmirtes Jun 19, 2026
125cf22
Call handleDefaultTruthyOrFalseyContext on $this->typeSpecifier in mi…
ondrejmirtes Jun 20, 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
8 changes: 1 addition & 7 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ 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
Expand Down Expand Up @@ -69,7 +63,7 @@ parameters:
-
rawMessage: Casting to string something that's already string.
identifier: cast.useless
count: 3
count: 5
path: src/Analyser/MutatingScope.php

-
Expand Down
61 changes: 61 additions & 0 deletions src/Analyser/ArgsResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Reflection\ParametersAcceptor;

/**
* 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 expression's
* stored return type. Null when the call had no variants (dynamic callee).
*/
final class ArgsResult
{

public function __construct(
private ExpressionResult $expressionResult,
private ?ParametersAcceptor $resolvedParametersAcceptor,
)
{
}

public function getScope(): MutatingScope
{
return $this->expressionResult->getScope();
}

public function hasYield(): bool
{
return $this->expressionResult->hasYield();
}

public function isAlwaysTerminating(): bool
{
return $this->expressionResult->isAlwaysTerminating();
}

/**
* @return InternalThrowPoint[]
*/
public function getThrowPoints(): array
{
return $this->expressionResult->getThrowPoints();
}

/**
* @return ImpurePoint[]
*/
public function getImpurePoints(): array
{
return $this->expressionResult->getImpurePoints();
}

public function getResolvedParametersAcceptor(): ?ParametersAcceptor
{
return $this->resolvedParametersAcceptor;
}

}
30 changes: 13 additions & 17 deletions src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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 @@ -38,8 +40,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 @@ -77,6 +81,7 @@ public function create(
$this->propertyReflectionFinder,
$this->parser,
$this->constantResolver,
$this->expressionResultStorageStack,
$context,
$this->phpVersion,
$this->attributeReflectionFactory,
Expand All @@ -102,25 +107,15 @@ public function create(

public function toFiberFactory(): InternalScopeFactory
{
return new self(
$this->container,
$this->reflectionProvider,
$this->initializerExprTypeResolver,
$this->expressionTypeResolverExtensionRegistryProvider,
$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 @@ -136,7 +131,8 @@ public function toMutatingFactory(): InternalScopeFactory
$this->configPhpVersion,
$this->nodeCallback,
$this->constantResolver,
false,
$fiber,
$this->expressionResultStorageStack,
);
}

Expand Down
16 changes: 0 additions & 16 deletions src/Analyser/ExprHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PHPStan\Type\Type;

/**
* @template T of Expr
Expand All @@ -32,19 +31,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;

}
99 changes: 51 additions & 48 deletions src/Analyser/ExprHandler/ArrayDimFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultFactory;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper;
use PHPStan\Analyser\IssetabilityDescriptor;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\NoopNodeCallback;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\Expr\TypeExpr;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_merge;

/**
Expand All @@ -35,59 +36,37 @@
final class ArrayDimFetchHandler implements ExprHandler
{

public function supports(Expr $expr): bool
public function __construct(
private ExpressionResultFactory $expressionResultFactory,
private DefaultNarrowingHelper $defaultNarrowingHelper,
)
{
return $expr instanceof ArrayDimFetch;
}

public function resolveType(MutatingScope $scope, Expr $expr): Type
public function supports(Expr $expr): bool
{
if ($expr->dim === null) {
return new NeverType();
}

$offsetAccessibleType = $scope->getType($expr->var);
if (
!$offsetAccessibleType->isArray()->yes()
&& (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()
) {
return NullsafeShortCircuitingHelper::getType(
$scope,
$expr->var,
$scope->getType(
new MethodCall(
$expr->var,
new Identifier('offsetGet'),
[
new Arg($expr->dim),
],
),
),
);
}

$offsetType = $scope->getType($expr->dim);
return NullsafeShortCircuitingHelper::getType(
$scope,
$expr->var,
$offsetAccessibleType->getOffsetValueType($offsetType),
);
return $expr instanceof ArrayDimFetch;
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$beforeScope = $scope;
if ($expr->dim === null) {
$varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep());
$scope = $varResult->getScope();

return new ExpressionResult(
return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
expr: $expr,
hasYield: $varResult->hasYield(),
isAlwaysTerminating: $varResult->isAlwaysTerminating(),
throwPoints: $varResult->getThrowPoints(),
impurePoints: $varResult->getImpurePoints(),
truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr),
falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr),
containsNullsafe: $varResult->containsNullsafe(),
// `$arr[]` only appears as an assignment target; reading it is a NeverType
typeCallback: static fn (): Type => new NeverType(),
specifyTypesCallback: fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $context),
);
}

Expand All @@ -97,7 +76,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$impurePoints = array_merge($dimResult->getImpurePoints(), $varResult->getImpurePoints());
$scope = $varResult->getScope();

$varType = $scope->getType($expr->var);
$varType = $varResult->getTypeForScope($scope);
$offsetGetResult = null;
if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) {
$throwPoints = array_merge($throwPoints, $nodeScopeResolver->processExprNode(
$stmt,
Expand All @@ -107,22 +87,45 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
new NoopNodeCallback(),
$context,
)->getThrowPoints());
// process the offsetGet here (storage is available, so the result is
// captured - not the storage - avoiding a reference cycle) so the
// typeCallback reads its result instead of Scope::getType(). Gated by
// the same maybe-ArrayAccess condition, so plain arrays never reach it.
$offsetGetResult = $nodeScopeResolver->processExprOnDemand(
new MethodCall($expr->var, new Identifier('offsetGet'), [new Arg($expr->dim)]),
$scope,
$storage,
);
}

return new ExpressionResult(
return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
expr: $expr,
hasYield: $dimResult->hasYield() || $varResult->hasYield(),
isAlwaysTerminating: $dimResult->isAlwaysTerminating() || $varResult->isAlwaysTerminating(),
throwPoints: $throwPoints,
impurePoints: $impurePoints,
truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr),
falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr),
);
}
containsNullsafe: $varResult->containsNullsafe(),
issetabilityDescriptor: IssetabilityDescriptor::offset($varResult, $dimResult),
typeCallback: static function (MutatingScope $s) use ($varResult, $dimResult, $offsetGetResult): Type {
$offsetAccessibleType = $varResult->getTypeForScope($s);
$shortCircuit = static fn (Type $type): Type => $varResult->containsNullsafe() && TypeCombinator::containsNull($offsetAccessibleType)
? TypeCombinator::addNull($type)
: $type;

public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes
{
return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context);
if (
$offsetGetResult !== null
&& !$offsetAccessibleType->isArray()->yes()
&& (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()
) {
return $shortCircuit($offsetGetResult->getTypeForScope($s));
}

return $shortCircuit($offsetAccessibleType->getOffsetValueType($dimResult->getTypeForScope($s)));
},
specifyTypesCallback: fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $context),
);
}

}
Loading
Loading