Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
227 commits
Select commit Hold shift + click to select a range
e9f7032
Introduce ExpressionResultFactory
ondrejmirtes Jun 11, 2026
8c7ae57
ExpressionResult - add beforeScope
ondrejmirtes Jun 11, 2026
6fee1ff
ExpressionResult - add Expr
ondrejmirtes Jun 11, 2026
6bdb410
Store ExpressionResult instead of before-Scope
ondrejmirtes Jun 11, 2026
588df03
Fill the missing gaps in expr processing
ondrejmirtes Jun 12, 2026
b6b6e8a
Divide ExprHandler into TypeResolvingExprHandler
ondrejmirtes Jun 12, 2026
dd67602
ScalarHandler stops implementing TypeResolvingExprHandler
ondrejmirtes Jun 12, 2026
19bb198
ExpressionResultStorageStack - answer type questions from ExpressionR…
ondrejmirtes Jun 12, 2026
15aa03c
Migrate ArrayHandler - per-item types from ExpressionResults
ondrejmirtes Jun 12, 2026
599fc28
Throw on unbalanced ExpressionResultStorageStack pop
ondrejmirtes Jun 12, 2026
d1e2b3b
Fix PHP 7.4 compat
ondrejmirtes Jun 12, 2026
11a1d07
Migrate VariableHandler and InstanceofHandler - narrowing from Expres…
ondrejmirtes Jun 12, 2026
f382742
Add regression tests for evaluation-point array item types
ondrejmirtes Jun 12, 2026
550d95b
Add regression test for certainty of undefined variables in loops
ondrejmirtes Jun 12, 2026
51deb6d
Store expressions even without FNSR
ondrejmirtes Jun 12, 2026
21dcc48
Only sureNot specifications skip certainly-undefined variables
ondrejmirtes Jun 12, 2026
654c877
This is better
ondrejmirtes Jun 12, 2026
bfc610d
ExpressionResult::createTypesCallback - the inside-out TypeSpecifier:…
ondrejmirtes Jun 12, 2026
aa28eb9
Coalesce, Ternary, BooleanAnd, BooleanOr stop implementing TypeResolv…
ondrejmirtes Jun 12, 2026
f005680
Add regression test for conditional holders narrowing coalesce of pro…
ondrejmirtes Jun 12, 2026
ed08235
Never process expressions on a FiberScope
ondrejmirtes Jun 12, 2026
d42aff1
Convert rule-facing FiberScope at the new-world hook boundary
ondrejmirtes Jun 12, 2026
2587701
Guard that only synthetic nodes reach the pending-fiber on-demand path
ondrejmirtes Jun 13, 2026
431e5f5
Fix immediately-invoked-closure fiber flush; make the new-world guard…
ondrejmirtes Jun 13, 2026
7351d1b
Flush pending fibers only at scope boundaries; process dropped call args
ondrejmirtes Jun 13, 2026
adaa5c3
Add PHPSTAN_GUARD_NW guard: no getType on a real node before processE…
ondrejmirtes Jun 13, 2026
d6b3c92
BitwiseNotHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
8d151b7
One more should-not
ondrejmirtes Jun 16, 2026
eda05b3
UnaryMinusHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
eb4b215
UnaryPlusHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
acc3c91
ConstFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
b5c29f7
PrintHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
3bc71f9
ThrowHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
4589795
ExitHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
fce87de
EvalHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
0d2fa0d
IncludeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
c90ea09
YieldFromHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
6cd3f21
ClassConstFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
c8a4309
InterpolatedStringHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
929b1c6
CloneHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
330da0c
YieldHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
53fcade
AlwaysRememberedExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
c91d086
NativeTypeExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
caa5ff7
FunctionCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
f0054bf
MethodCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
58954c5
StaticMethodCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
6c97d97
InstantiationCallableNodeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
55b9280
TypeExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 16, 2026
2743fa6
Fix failure of forwarding ExpressionResult
ondrejmirtes Jun 17, 2026
8371ae9
ErrorSuppressHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
21a9eff
CastHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
fc33122
CastStringHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
7a883f1
PostIncHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
52695bb
PostDecHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
00dde49
PipeHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
25f3866
PreIncHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
1bad5d7
PreDecHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
bc9efa8
AssignOpHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
064c0fa
AssignHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
a4fbe8c
Track containsNullsafe on ExpressionResult and propagate it through f…
ondrejmirtes Jun 17, 2026
21b08cb
PropertyFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
34f9ec9
StaticPropertyFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
b59c3b6
Store ArrayDimFetch assign-target results with a typeCallback
ondrejmirtes Jun 17, 2026
a74be4c
ArrayDimFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
c20043a
Introduce IssetabilityDescriptor and fold it in MutatingScope::issetC…
ondrejmirtes Jun 17, 2026
600c864
Move issetCheck onto ExpressionResult with isset()/empty() convenience
ondrejmirtes Jun 17, 2026
1e0208f
CoalesceHandler reads issetCheck from the left result instead of the …
ondrejmirtes Jun 17, 2026
5a672a7
EmptyHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
fca91b8
Match issetCheckUndefined ordering in IssetabilityDescriptor::checkUn…
ondrejmirtes Jun 17, 2026
9d8a231
Re-evaluate getCurrentTypesOfSpecifiedExpr on the asking scope
ondrejmirtes Jun 17, 2026
9e52470
Rules\IssetCheck folds IssetabilityDescriptor instead of re-walking t…
ondrejmirtes Jun 17, 2026
d3c8d42
MatchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 17, 2026
18f25ff
NullsafePropertyFetchHandler and NullsafeMethodCallHandler are no lon…
ondrejmirtes Jun 17, 2026
118c664
Move first-class callable type resolution into the *CallableNode hand…
ondrejmirtes Jun 17, 2026
1c5d0c7
Price synthetic narrowing expressions on demand in getCurrentTypesOfS…
ondrejmirtes Jun 17, 2026
7c69349
BinaryOpHandler and BooleanNotHandler are no longer TypeResolvingExpr…
ondrejmirtes Jun 17, 2026
b4d8bcf
Fix getIssetabilityDescriptor shadowed by descriptor-less assignment-…
ondrejmirtes Jun 17, 2026
b72bea0
Eliminate the OriginalPropertyTypeExpr virtual node
ondrejmirtes Jun 18, 2026
92c8235
Eliminate the GetOffsetValueTypeExpr virtual node
ondrejmirtes Jun 18, 2026
23ef195
Eliminate the GetIterableKeyTypeExpr virtual node
ondrejmirtes Jun 18, 2026
e67f8c8
Eliminate the GetIterableValueTypeExpr virtual node
ondrejmirtes Jun 18, 2026
7558d83
ExistingArrayDimFetchHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 18, 2026
e260a75
UnsetOffsetExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 18, 2026
85e45e9
SetOffsetValueTypeExprHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 18, 2026
30fb27e
SetExistingOffsetValueTypeExprHandler is no longer TypeResolvingExprH…
ondrejmirtes Jun 18, 2026
7c89b06
Read operand types from ExpressionResults in Throw/BooleanAnd/Coalesc…
ondrejmirtes Jun 18, 2026
cd2772f
Read operand types from ExpressionResults in Ternary/ArrayDimFetch/Pr…
ondrejmirtes Jun 18, 2026
c3f94d2
Read sub-expression types from ExpressionResults in offset Virtual ha…
ondrejmirtes Jun 18, 2026
3106527
Read child/narrowed types from results instead of Scope::getType in l…
ondrejmirtes Jun 18, 2026
5bbfe5b
Process synthetic offsetGet/callable nodes in processExpr, read resul…
ondrejmirtes Jun 18, 2026
ff1f664
Add readStoredOrPriceOnDemand/priceSyntheticOnDemand and use them ins…
ondrejmirtes Jun 18, 2026
1553ba7
Price synthetic unary-minus operand on demand instead of Scope::getType
ondrejmirtes Jun 18, 2026
7b59fa8
Read child/synthetic types via result or helpers in Match/BinaryOp/Eq…
ondrejmirtes Jun 18, 2026
4a9cb9d
Thread NodeScopeResolver into narrowing/throw-point helpers to avoid …
ondrejmirtes Jun 18, 2026
6ced5c5
Read child/synthetic types via results or helpers in AssignHandler's …
ondrejmirtes Jun 18, 2026
1d95aeb
Read expr types via results or helpers in NodeScopeResolver instead o…
ondrejmirtes Jun 18, 2026
e55e051
Correct the explanation of the two load-bearing Scope::getType() exce…
ondrejmirtes Jun 18, 2026
93050be
Read Identical operand types from results in RicherScopeGetTypeHelper…
ondrejmirtes Jun 18, 2026
76a1f75
Read expr types from results in SpecifiedTypes::normalize when called…
ondrejmirtes Jun 18, 2026
f92abeb
Read assign-target sub-expression types from their results instead of…
ondrejmirtes Jun 18, 2026
df526d0
Read while-loop condition type from its result instead of the on-dema…
ondrejmirtes Jun 18, 2026
7039451
Pass already-computed results into ImplicitToStringCallHelper and rea…
ondrejmirtes Jun 18, 2026
9c42e43
NSRT test for precise Scope
ondrejmirtes Jun 18, 2026
4a06cdb
Extract intrinsic argument parameter overrides from selectFromArgs in…
ondrejmirtes Jun 18, 2026
3d32271
Resolve argument types on an arg-to-arg evolving scope and select the…
ondrejmirtes Jun 19, 2026
bedae79
FuncCallHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
aba7554
MethodCallHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
5174a3b
NewHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
5863cce
StaticCallHandler is no longer TypeResolvingExprHandler
ondrejmirtes Jun 19, 2026
b0ffdaf
Extract combineVariantsForNormalization into a shared ParametersAccep…
ondrejmirtes Jun 19, 2026
6590ffc
Add regression test for #13253
ondrejmirtes Jun 19, 2026
2564336
Add regression test for #14396
ondrejmirtes Jun 19, 2026
186daa9
Call handleDefaultTruthyOrFalseyContext on $this->typeSpecifier in mi…
ondrejmirtes Jun 20, 2026
c021269
Rework issetCheck with single-pass
ondrejmirtes Jun 22, 2026
044ce61
Resolve isset() narrowing types from computed results, not Scope::get…
ondrejmirtes Jun 22, 2026
65b846b
ClosureHandler and ArrowFunctionHandler are no longer TypeResolvingEx…
ondrejmirtes Jun 22, 2026
cf8eb06
Build the closure and arrow function type from the single body walk
ondrejmirtes Jun 22, 2026
72f231f
Enter closure and arrow function scopes without re-walking the body
ondrejmirtes Jun 22, 2026
f690dd4
Simplify ExpressionResult::getType methods now that TypeResolvingExpr…
ondrejmirtes Jun 22, 2026
0b52694
Throw on unhandled expr
ondrejmirtes Jun 22, 2026
1d531be
Resolve the IssetExpr virtual node through an ExprHandler
ondrejmirtes Jun 22, 2026
217a582
Do not ensure non-nullability of the isset/empty/?? operand itself
ondrejmirtes Jun 22, 2026
67b1c8c
Decide the nullsafe short-circuit from the receiver's real type
ondrejmirtes Jun 23, 2026
04e020f
Resolve the PossiblyImpureCallExpr virtual node through an ExprHandler
ondrejmirtes Jun 23, 2026
0eec1bc
Resolve the backtick operator through an ExprHandler
ondrejmirtes Jun 23, 2026
8665d9a
Keep the narrowed type when applySpecifiedTypes changes isset certainty
ondrejmirtes Jun 23, 2026
e9b853a
Resolve a maybe-existing property to its declared type instead of Err…
ondrejmirtes Jun 23, 2026
2bbd42f
Treat a property with a default value as always set in isset resolution
ondrejmirtes Jun 23, 2026
f777476
Revert "Simplify ExpressionResult::getType methods now that TypeResol…
ondrejmirtes Jun 23, 2026
a884b93
Compose AlwaysRememberedExpr narrowing inside-out via createTypesCall…
ondrejmirtes Jun 23, 2026
1276718
Compose nullsafe narrowing inside-out via createTypesCallback
ondrejmirtes Jun 23, 2026
45ae435
Compose method/static call narrowing inside-out via createTypesCallback
ondrejmirtes Jun 23, 2026
05a7918
Compose function call narrowing inside-out via createTypesCallback
ondrejmirtes Jun 23, 2026
69e850d
Gate the nullsafe createTypesCallback on containsNull
ondrejmirtes Jun 23, 2026
88fbb9a
Compose equality operand narrowing inside-out via createSubjectTypes
ondrejmirtes Jun 23, 2026
88c3742
Route all of EqualityTypeSpecifyingHelper's narrowing through createF…
ondrejmirtes Jun 24, 2026
bb8d406
Route BinaryOp / Isset / conditional-holder narrowing through createF…
ondrejmirtes Jun 24, 2026
df1857b
Resolve child narrowing on demand instead of via specifyTypesInCondition
ondrejmirtes Jun 24, 2026
5c466b3
Resolve BinaryOpHandler re-dispatched conditions on demand
ondrejmirtes Jun 24, 2026
496dc27
Resolve equality re-dispatched conditions on demand
ondrejmirtes Jun 24, 2026
0ab68ba
Resolve empty()'s isset-or-falsey condition on demand
ondrejmirtes Jun 24, 2026
dcedf61
Compose negation narrowing from the operand result; isset on demand
ondrejmirtes Jun 24, 2026
76d0f7f
Thread operand results into equality narrowing
ondrejmirtes Jun 24, 2026
41ad08d
Compose remembered-operand narrowing from the result, dropping manual…
ondrejmirtes Jun 24, 2026
bdb9f33
Drop the AlwaysRememberedExpr fan-out from TypeSpecifier::create
ondrejmirtes Jun 24, 2026
0ed82e8
Resolve conditional-after-assign narrowing on demand
ondrejmirtes Jun 24, 2026
2629ef1
Resolve nullsafe receiver-and-fetch condition on demand
ondrejmirtes Jun 24, 2026
1768736
Read expression-statement narrowing from its result
ondrejmirtes Jun 24, 2026
8eda3ed
Resolve the impossible-check condition through the scope's dispatcher
ondrejmirtes Jun 24, 2026
bf064f9
Apply the expression-statement narrowing via applySpecifiedTypes
ondrejmirtes Jun 24, 2026
f4f87b9
Apply do-while exit narrowing from the condition result
ondrejmirtes Jun 24, 2026
6160e8d
Narrow control-flow scopes via applySpecifiedTypes, not filterBy*Value
ondrejmirtes Jun 24, 2026
a9bfd07
Require typeCallback and specifyTypesCallback on ExpressionResult
ondrejmirtes Jun 24, 2026
444bb63
Remove todo
ondrejmirtes Jun 24, 2026
ad301fc
Replace handleDefaultTruthyOrFalseyContext with specifyDefaultTypes
ondrejmirtes Jun 24, 2026
d94d8aa
Read operand/receiver types from their results, not Scope::getType
ondrejmirtes Jun 24, 2026
3942ecd
Guard on-demand pricing paths with PHPSTAN_GUARD_NW
ondrejmirtes Jun 24, 2026
408c894
Capture per-arg ExpressionResults; read appending-arg types from them
ondrejmirtes Jun 24, 2026
652e8f1
Read array_splice and resolveReturnType arg types from their results
ondrejmirtes Jun 24, 2026
693181b
Carry the ??= left side's isset descriptor into its coalesce type
ondrejmirtes Jun 24, 2026
b0911b3
Find the early-terminating expression after processing the statement
ondrejmirtes Jun 24, 2026
be2db70
Consume the processed arg result for the callable-arg check in proces…
ondrejmirtes Jun 24, 2026
cf35bc7
Resolve the arrow function return type after its body is processed
ondrejmirtes Jun 24, 2026
9fce1ed
Compute Closure::bind's bound scope from its processed arguments
ondrejmirtes Jun 25, 2026
f45f13b
Fix PHPStan self-analysis errors in NodeScopeResolver
ondrejmirtes Jun 25, 2026
addd909
Read impossible-check narrowing from the call's ExpressionResult
ondrejmirtes Jun 25, 2026
dd6e734
Gate the processArgs fast path on parameter late-resolvability only
ondrejmirtes Jun 25, 2026
8ede1c3
Don't route curl_setopt(_array) through the metadata pre-pass
ondrejmirtes Jun 25, 2026
a29d8b6
Don't route implode/join through the metadata pre-pass
ondrejmirtes Jun 25, 2026
1986749
Supply array_map's callback parameter type via a closure-type extension
ondrejmirtes Jun 25, 2026
84d3c5a
Supply array_filter/walk/find callback parameter types via closure-ty…
ondrejmirtes Jun 25, 2026
fe69de0
Read array_walk's original array type after its argument is processed
ondrejmirtes Jun 25, 2026
78d2695
Make Closure::bind/bindTo single-pass; drop the empty argsHaveIntrins…
ondrejmirtes Jun 25, 2026
c141cde
Resolve single-variant generic acceptors incrementally so they are si…
ondrejmirtes Jun 25, 2026
1ec2c66
Select multi-variant acceptors by argument count so processArgs is si…
ondrejmirtes Jun 25, 2026
4814c18
Deduplicate constant-condition vs impossible-check reports via collec…
ondrejmirtes Jun 25, 2026
e79d4e9
Process a dynamic function-call name before reading its type
ondrejmirtes Jun 25, 2026
3ee8fbe
Process a nested array-dimension before reading its type in AssignHan…
ondrejmirtes Jun 25, 2026
5b27db2
Process an assign-op Variable/property target as a read before compos…
ondrejmirtes Jun 25, 2026
46d3358
Process Closure::call's new-$this argument before reading its type
ondrejmirtes Jun 25, 2026
a07c2ce
Process clone()'s arguments before reading them in clone-with handling
ondrejmirtes Jun 25, 2026
20c8342
Evaluate an array-dim assignment's value before reading its type
ondrejmirtes Jun 26, 2026
4abe730
Read the foreach value variable's narrowed type by name in the loop a…
ondrejmirtes Jun 26, 2026
34ee5a6
Carry the nullsafe receiver type to its rule via a virtual node
ondrejmirtes Jun 26, 2026
9364b53
Drop the dead Expr parameter from ExpressionResult's typeCallback
ondrejmirtes Jun 26, 2026
248165e
Build pre-inc/dec literal types via ConstantTypeHelper, not the scope
ondrejmirtes Jun 26, 2026
24d98eb
Resolve lexical context once at create()-time in three type callbacks
ondrejmirtes Jun 26, 2026
ab22782
Resolve the instanceof Name class type once at create()-time
ondrejmirtes Jun 26, 2026
b11cd2b
Reprocess the switch subject for the exhaustiveness check instead of …
ondrejmirtes Jun 26, 2026
c11c748
Reprocess the foreach iteratee and while condition on their narrowed …
ondrejmirtes Jun 26, 2026
b607f3e
Reprocess coalesce/ternary/match subjects on their narrowed scopes in…
ondrejmirtes Jun 26, 2026
29381be
Reprocess the foreach iteratee on the post-loop scope for value/key-t…
ondrejmirtes Jun 26, 2026
be10e9a
Narrow the pinned-name property fetch via applySpecifiedTypes, not fi…
ondrejmirtes Jun 26, 2026
733c16b
Make specifyTypesCallback required and getSpecifiedTypesForScope non-…
ondrejmirtes Jun 26, 2026
a8f7dca
Narrow synthetic conditions via processExprOnDemand, not filterByTrut…
ondrejmirtes Jun 26, 2026
3806927
Read wrapped operand types via getType/getNativeType in cast/clone/un…
ondrejmirtes Jun 27, 2026
4f649c1
Read child types via getType/getNativeType in post/pre-inc/dec, pipe,…
ondrejmirtes Jun 27, 2026
2bde9c2
Read the ternary condition type via getType/getNativeType in the type…
ondrejmirtes Jun 27, 2026
c99b8e2
Propagate the assigned value into byref-intertwined variables instead…
ondrejmirtes Jun 27, 2026
a4687b9
Read binary-op operands via getType/getNativeType in the type callback
ondrejmirtes Jun 27, 2026
63e4acc
Read the left operand boolean via getType/getNativeType in boolean ty…
ondrejmirtes Jun 27, 2026
2658b5e
Resolve property fetch types without the asking scope
ondrejmirtes Jun 27, 2026
fc18100
Resolve static property fetch types without the asking scope
ondrejmirtes Jun 27, 2026
57c3b05
Resolve method call return types without the asking scope
ondrejmirtes Jun 27, 2026
117939b
Resolve static call return types without the asking scope
ondrejmirtes Jun 27, 2026
65391df
Resolve function call return types without the asking scope
ondrejmirtes Jun 27, 2026
82e7872
Resolve nullsafe fetch/call short-circuit types without the asking scope
ondrejmirtes Jun 27, 2026
155584e
Read assignment and class-constant-fetch type callbacks via getType/g…
ondrejmirtes Jun 27, 2026
9cda04c
Read instanceof and coalesce type callbacks without the asking scope
ondrejmirtes Jun 27, 2026
6314fe0
Read assign-op type callback without the asking scope
ondrejmirtes Jun 27, 2026
b822915
Pass the iteratee types into enterForeach instead of re-reading them
ondrejmirtes Jun 27, 2026
7f58fd7
Pass the iteratee types into enterForeachKey instead of re-reading them
ondrejmirtes Jun 27, 2026
31cf14b
Make the ExpressionResult typeCallback resolve from a native flag, no…
ondrejmirtes Jun 27, 2026
52242b1
Require an ExpressionResult to have either a precomputed type or a ty…
ondrejmirtes Jun 27, 2026
e2085d5
Read the tracked holder directly in ExpressionResult, not via Scope::…
ondrejmirtes Jun 27, 2026
1e2f939
Get rid of truthyScopeCallback and falseyScopeCallback
ondrejmirtes Jun 27, 2026
460e9c9
Use the operand's own truthy/falsey scope for &&/|| narrowing
ondrejmirtes Jun 27, 2026
c13b5d3
Record conditional holders from by-ref-updated variables via scope state
ondrejmirtes Jun 28, 2026
e16b537
Derive statement exit points from never instead of findEarlyTerminati…
ondrejmirtes Jun 28, 2026
30fbee6
Narrower return type
ondrejmirtes Jun 29, 2026
fa6bb8f
Do not use getChildSpecifiedTypes
ondrejmirtes Jun 29, 2026
de9d53c
Inline getSpecifiedTypesForScope instead of getChildSpecifiedTypes
ondrejmirtes Jun 30, 2026
736399b
Keep getType for the dropped-self-condition complement
ondrejmirtes Jun 30, 2026
845a34b
Add regression test for the type of a Closure::bind callback
ondrejmirtes Jun 30, 2026
2676e64
Add regression test for array value type after foreach by-ref reassig…
ondrejmirtes Jun 30, 2026
fa2aa77
Add regression test for list<non-empty-array> preserved after foreach…
ondrejmirtes Jun 30, 2026
fb28d43
Add regression test for ??= on a dynamic property array offset
ondrejmirtes Jun 30, 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
76 changes: 76 additions & 0 deletions src/Analyser/ArgsResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php declare(strict_types = 1);

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 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();
}

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 (bool $nativeTypesPromoted) use ($varResult, $dimResult, $offsetGetResult): Type {
$offsetAccessibleType = ($nativeTypesPromoted ? $varResult->getNativeType() : $varResult->getType());
$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(($nativeTypesPromoted ? $offsetGetResult->getNativeType() : $offsetGetResult->getType()));
}

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

}
Loading