Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
378 commits
Select commit Hold shift + click to select a range
d5a984c
Revert "Simplify ExpressionResult::getType methods now that TypeResol…
ondrejmirtes Jun 23, 2026
c5f2716
Compose AlwaysRememberedExpr narrowing inside-out via createTypesCall…
ondrejmirtes Jun 23, 2026
cde6f76
Compose nullsafe narrowing inside-out via createTypesCallback
ondrejmirtes Jun 23, 2026
ef89073
Compose method/static call narrowing inside-out via createTypesCallback
ondrejmirtes Jun 23, 2026
82afa50
Compose function call narrowing inside-out via createTypesCallback
ondrejmirtes Jun 23, 2026
1c698fa
Gate the nullsafe createTypesCallback on containsNull
ondrejmirtes Jun 23, 2026
6954ac7
Compose equality operand narrowing inside-out via createSubjectTypes
ondrejmirtes Jun 23, 2026
30c2378
Route all of EqualityTypeSpecifyingHelper's narrowing through createF…
ondrejmirtes Jun 24, 2026
26e81c4
Route BinaryOp / Isset / conditional-holder narrowing through createF…
ondrejmirtes Jun 24, 2026
9c68a16
Resolve child narrowing on demand instead of via specifyTypesInCondition
ondrejmirtes Jun 24, 2026
0bf9be6
Resolve BinaryOpHandler re-dispatched conditions on demand
ondrejmirtes Jun 24, 2026
618b252
Resolve equality re-dispatched conditions on demand
ondrejmirtes Jun 24, 2026
32ad245
Resolve empty()'s isset-or-falsey condition on demand
ondrejmirtes Jun 24, 2026
d96e467
Compose negation narrowing from the operand result; isset on demand
ondrejmirtes Jun 24, 2026
7e44e88
Thread operand results into equality narrowing
ondrejmirtes Jun 24, 2026
16004b3
Compose remembered-operand narrowing from the result, dropping manual…
ondrejmirtes Jun 24, 2026
e9f9840
Drop the AlwaysRememberedExpr fan-out from TypeSpecifier::create
ondrejmirtes Jun 24, 2026
8fcdf21
Resolve conditional-after-assign narrowing on demand
ondrejmirtes Jun 24, 2026
b4eb6b4
Resolve nullsafe receiver-and-fetch condition on demand
ondrejmirtes Jun 24, 2026
d7e292f
Read expression-statement narrowing from its result
ondrejmirtes Jun 24, 2026
78b5dad
Resolve the impossible-check condition through the scope's dispatcher
ondrejmirtes Jun 24, 2026
f08846e
Apply the expression-statement narrowing via applySpecifiedTypes
ondrejmirtes Jun 24, 2026
99241d9
Apply do-while exit narrowing from the condition result
ondrejmirtes Jun 24, 2026
83064b5
Narrow control-flow scopes via applySpecifiedTypes, not filterBy*Value
ondrejmirtes Jun 24, 2026
b8388ca
Require typeCallback and specifyTypesCallback on ExpressionResult
ondrejmirtes Jun 24, 2026
8d48d17
Remove todo
ondrejmirtes Jun 24, 2026
41aa004
Replace handleDefaultTruthyOrFalseyContext with specifyDefaultTypes
ondrejmirtes Jun 24, 2026
baef4f1
Read operand/receiver types from their results, not Scope::getType
ondrejmirtes Jun 24, 2026
5a7389c
Guard on-demand pricing paths with PHPSTAN_GUARD_NW
ondrejmirtes Jun 24, 2026
b3c00ea
Capture per-arg ExpressionResults; read appending-arg types from them
ondrejmirtes Jun 24, 2026
0a3f04d
Read array_splice and resolveReturnType arg types from their results
ondrejmirtes Jun 24, 2026
9ed412b
Carry the ??= left side's isset descriptor into its coalesce type
ondrejmirtes Jun 24, 2026
681f224
Find the early-terminating expression after processing the statement
ondrejmirtes Jun 24, 2026
184385c
Consume the processed arg result for the callable-arg check in proces…
ondrejmirtes Jun 24, 2026
28c9e41
Resolve the arrow function return type after its body is processed
ondrejmirtes Jun 24, 2026
f5d2f1f
Compute Closure::bind's bound scope from its processed arguments
ondrejmirtes Jun 25, 2026
b29d3ca
Fix PHPStan self-analysis errors in NodeScopeResolver
ondrejmirtes Jun 25, 2026
9e99955
Read impossible-check narrowing from the call's ExpressionResult
ondrejmirtes Jun 25, 2026
3987447
Gate the processArgs fast path on parameter late-resolvability only
ondrejmirtes Jun 25, 2026
8e6e7f0
Don't route curl_setopt(_array) through the metadata pre-pass
ondrejmirtes Jun 25, 2026
1c46542
Don't route implode/join through the metadata pre-pass
ondrejmirtes Jun 25, 2026
1a60e43
Supply array_map's callback parameter type via a closure-type extension
ondrejmirtes Jun 25, 2026
82b3c23
Supply array_filter/walk/find callback parameter types via closure-ty…
ondrejmirtes Jun 25, 2026
97e0686
Read array_walk's original array type after its argument is processed
ondrejmirtes Jun 25, 2026
ea774a3
Make Closure::bind/bindTo single-pass; drop the empty argsHaveIntrins…
ondrejmirtes Jun 25, 2026
5a89b02
Resolve single-variant generic acceptors incrementally so they are si…
ondrejmirtes Jun 25, 2026
53cf4b8
Select multi-variant acceptors by argument count so processArgs is si…
ondrejmirtes Jun 25, 2026
379b8a7
Deduplicate constant-condition vs impossible-check reports via collec…
ondrejmirtes Jun 25, 2026
1411031
Process a dynamic function-call name before reading its type
ondrejmirtes Jun 25, 2026
e13d727
Process a nested array-dimension before reading its type in AssignHan…
ondrejmirtes Jun 25, 2026
fd0cb91
Process an assign-op Variable/property target as a read before compos…
ondrejmirtes Jun 25, 2026
05cb725
Process Closure::call's new-$this argument before reading its type
ondrejmirtes Jun 25, 2026
536392d
Process clone()'s arguments before reading them in clone-with handling
ondrejmirtes Jun 25, 2026
4f47074
Evaluate an array-dim assignment's value before reading its type
ondrejmirtes Jun 26, 2026
8268166
Read the foreach value variable's narrowed type by name in the loop a…
ondrejmirtes Jun 26, 2026
5b8d9db
Carry the nullsafe receiver type to its rule via a virtual node
ondrejmirtes Jun 26, 2026
c3bc1ff
Drop the dead Expr parameter from ExpressionResult's typeCallback
ondrejmirtes Jun 26, 2026
3fce90c
Build pre-inc/dec literal types via ConstantTypeHelper, not the scope
ondrejmirtes Jun 26, 2026
9676d5c
Resolve lexical context once at create()-time in three type callbacks
ondrejmirtes Jun 26, 2026
1515c80
Resolve the instanceof Name class type once at create()-time
ondrejmirtes Jun 26, 2026
3a7f381
Reprocess the switch subject for the exhaustiveness check instead of …
ondrejmirtes Jun 26, 2026
15dc4a2
Reprocess the foreach iteratee and while condition on their narrowed …
ondrejmirtes Jun 26, 2026
34fdf94
Reprocess coalesce/ternary/match subjects on their narrowed scopes in…
ondrejmirtes Jun 26, 2026
28a4e2d
Reprocess the foreach iteratee on the post-loop scope for value/key-t…
ondrejmirtes Jun 26, 2026
f092ff8
Narrow the pinned-name property fetch via applySpecifiedTypes, not fi…
ondrejmirtes Jun 26, 2026
3f71bc8
Make specifyTypesCallback required and getSpecifiedTypesForScope non-…
ondrejmirtes Jun 26, 2026
98624ea
Narrow synthetic conditions via processExprOnDemand, not filterByTrut…
ondrejmirtes Jun 26, 2026
58840ae
Read wrapped operand types via getType/getNativeType in cast/clone/un…
ondrejmirtes Jun 27, 2026
000901e
Read child types via getType/getNativeType in post/pre-inc/dec, pipe,…
ondrejmirtes Jun 27, 2026
679e66e
Read the ternary condition type via getType/getNativeType in the type…
ondrejmirtes Jun 27, 2026
da43c40
Propagate the assigned value into byref-intertwined variables instead…
ondrejmirtes Jun 27, 2026
d284746
Read binary-op operands via getType/getNativeType in the type callback
ondrejmirtes Jun 27, 2026
c00cc98
Read the left operand boolean via getType/getNativeType in boolean ty…
ondrejmirtes Jun 27, 2026
9041160
Resolve property fetch types without the asking scope
ondrejmirtes Jun 27, 2026
7ecc6e1
Resolve static property fetch types without the asking scope
ondrejmirtes Jun 27, 2026
a6f24e7
Resolve method call return types without the asking scope
ondrejmirtes Jun 27, 2026
e529031
Resolve static call return types without the asking scope
ondrejmirtes Jun 27, 2026
c9108ba
Resolve function call return types without the asking scope
ondrejmirtes Jun 27, 2026
69c1b08
Resolve nullsafe fetch/call short-circuit types without the asking scope
ondrejmirtes Jun 27, 2026
26b6c9a
Read assignment and class-constant-fetch type callbacks via getType/g…
ondrejmirtes Jun 27, 2026
222ed3e
Read instanceof and coalesce type callbacks without the asking scope
ondrejmirtes Jun 27, 2026
2909134
Read assign-op type callback without the asking scope
ondrejmirtes Jun 27, 2026
8a4f8a2
Pass the iteratee types into enterForeach instead of re-reading them
ondrejmirtes Jun 27, 2026
b1d0c60
Pass the iteratee types into enterForeachKey instead of re-reading them
ondrejmirtes Jun 27, 2026
d1549ba
Make the ExpressionResult typeCallback resolve from a native flag, no…
ondrejmirtes Jun 27, 2026
cacc4e3
Require an ExpressionResult to have either a precomputed type or a ty…
ondrejmirtes Jun 27, 2026
7d6aad0
Read the tracked holder directly in ExpressionResult, not via Scope::…
ondrejmirtes Jun 27, 2026
4ac500f
Get rid of truthyScopeCallback and falseyScopeCallback
ondrejmirtes Jun 27, 2026
ce9d54b
Use the operand's own truthy/falsey scope for &&/|| narrowing
ondrejmirtes Jun 27, 2026
fa6df9e
Record conditional holders from by-ref-updated variables via scope state
ondrejmirtes Jun 28, 2026
103d013
Derive statement exit points from never instead of findEarlyTerminati…
ondrejmirtes Jun 28, 2026
c638a4d
Narrower return type
ondrejmirtes Jun 29, 2026
d3fe412
Do not use getChildSpecifiedTypes
ondrejmirtes Jun 29, 2026
bc3bc99
Inline getSpecifiedTypesForScope instead of getChildSpecifiedTypes
ondrejmirtes Jun 30, 2026
2df5e26
Keep getType for the dropped-self-condition complement
ondrejmirtes Jun 30, 2026
cc25fe5
Add regression test for the type of a Closure::bind callback
ondrejmirtes Jun 30, 2026
80924c2
Add regression test for array value type after foreach by-ref reassig…
ondrejmirtes Jun 30, 2026
be884aa
Add regression test for list<non-empty-array> preserved after foreach…
ondrejmirtes Jun 30, 2026
8a9e1cf
Add regression test for ??= on a dynamic property array offset
ondrejmirtes Jun 30, 2026
70476f4
Resolve ExpressionResult types from memoized per-flavour slots
ondrejmirtes Jul 2, 2026
6644eaf
Split readStoredOrPriceOnDemand into decisive primitives
ondrejmirtes Jul 2, 2026
0b22f94
Reintroduce readStoredResult as a storage-based assertion
ondrejmirtes Jul 2, 2026
a190af4
Narrow ?? by composing isset facts from chain results, not a syntheti…
ondrejmirtes Jul 2, 2026
c0efb63
Compose multi-subject isset() truthy narrowing from chain results
ondrejmirtes Jul 2, 2026
3e8eff2
Consume the match arms' captured scope/type pairs instead of re-walki…
ondrejmirtes Jul 2, 2026
29c5853
Answer plain variable reads from scope state instead of on-demand pro…
ondrejmirtes Jul 2, 2026
01cfe07
Run AnalyserTest through FiberNodeScopeResolver like the other test h…
ondrejmirtes Jul 2, 2026
a550495
Resolve the offset-write property holder without re-reading the receiver
ondrejmirtes Jul 2, 2026
ef0db15
Answer maybe-stored reads from a tracked whole-expression holder with…
ondrejmirtes Jul 2, 2026
f22bab9
Skip the null-containment probe for bare variable narrowing subjects
ondrejmirtes Jul 3, 2026
d29fd66
Pin the dropped-self-condition complement type at holder-build time
ondrejmirtes Jul 3, 2026
3e92356
Compose createSubjectTypes entries from the result's own facts, never…
ondrejmirtes Jul 3, 2026
56e1845
Collapse filterByTruthyValue/filterByFalseyValue onto applySpecifiedT…
ondrejmirtes Jul 3, 2026
4d665e0
Narrow match scopes via applySpecifiedTypes instead of filterBy*
ondrejmirtes Jul 3, 2026
7349060
Compute call narrowing on the evaluation scope, flavoured by the aski…
ondrejmirtes Jul 4, 2026
8ca03f1
Compose null-identity narrowing from operand results in IdenticalNarr…
ondrejmirtes Jul 4, 2026
95ca533
Narrow bool-literal identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
59b981f
Narrow scalar-literal identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
fcd671c
Compose array_key_first-family null narrowing through the FuncCall co…
ondrejmirtes Jul 4, 2026
955fa4d
Narrow get_class/get_debug_type identity comparisons in IdenticalNarr…
ondrejmirtes Jul 4, 2026
b200b57
Narrow gettype identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
0d5a1a6
Narrow preg_match identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
813a75c
Narrow strlen/mb_strlen identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
ce8ad99
Move count-size narrowing into CountNarrowingHelper
ondrejmirtes Jul 5, 2026
9b76f3f
Narrow count/sizeof identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
66d82f8
Narrow string-function identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
054509e
Narrow trim and get_parent_class identity comparisons in IdenticalNar…
ondrejmirtes Jul 5, 2026
9397f76
Narrow ::class identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
a33367f
Narrow general identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
a9cd707
Compose function-family narrowing for type-based constant sides
ondrejmirtes Jul 5, 2026
b149cf9
Narrow loose-equality comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
3041c05
Compose assign-time conditional holders from the assigned expression'…
ondrejmirtes Jul 5, 2026
cdb14dc
Read assign-time ternary holder types from the captured walk results
ondrejmirtes Jul 5, 2026
8d7d1a7
Extract conjunction narrowing into BooleanNarrowingHelper
ondrejmirtes Jul 5, 2026
e1ce90e
Fold multi-subject isset narrowing through the conjunction helper
ondrejmirtes Jul 5, 2026
561755e
Extract disjunction narrowing into BooleanNarrowingHelper
ondrejmirtes Jul 5, 2026
e4d106e
Share the single-subject isset narrowing in DefaultNarrowingHelper
ondrejmirtes Jul 5, 2026
8053322
Compose empty() narrowing through the disjunction helper
ondrejmirtes Jul 5, 2026
d8910d6
Compose ternary narrowing through the boolean helpers
ondrejmirtes Jul 5, 2026
8b4ab41
Compose cast narrowing through the loose-equality helper
ondrejmirtes Jul 5, 2026
52ad10e
Read tracked foreach and @var types without synthetic pricing
ondrejmirtes Jul 5, 2026
d1e0a49
Capture call-argument results for the composed comparison narrowing
ondrejmirtes Jul 5, 2026
f23e3ef
Narrow bool-typed identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
176195e
Merge both directions of call-vs-call identity comparisons
ondrejmirtes Jul 5, 2026
2dd0937
Compose the last identity-comparison tails
ondrejmirtes Jul 5, 2026
847ae5e
Delete the old-world equality narrowing
ondrejmirtes Jul 5, 2026
730b5e8
Key the closure type cache by the closure's actual inputs
ondrejmirtes Jul 5, 2026
5c04416
Read assign sentinel base result from the walk storage
ondrejmirtes Jul 5, 2026
60dc9c3
Compose comparison verdicts from captured results instead of re-walking
ondrejmirtes Jul 5, 2026
43c5b46
Compose nullsafe method call null-removal from the receiver's stored …
ondrejmirtes Jul 5, 2026
39b2538
Compose nullsafe property fetch null-removal from the receiver's stor…
ondrejmirtes Jul 5, 2026
4ff61af
Compose variable-variable narrowing from the name expression's result
ondrejmirtes Jul 5, 2026
1ec7c65
Read by-ref destructuring item types from the scope directly
ondrejmirtes Jul 5, 2026
fa16a1f
Compose match arm condition verdicts and narrowing from walk results
ondrejmirtes Jul 5, 2026
61b1534
Use default narrowing directly for bool-variable contradiction probing
ondrejmirtes Jul 5, 2026
0e9f037
Compose single-condition match arm filtering verdicts from walk results
ondrejmirtes Jul 6, 2026
2865496
Reuse the ensured-scope call result for the nullsafe maybe-null type
ondrejmirtes Jul 6, 2026
d4577e3
Extract the coalesce type and falsey-narrowing composition into a helper
ondrejmirtes Jul 6, 2026
637f339
Compose ??= type and narrowing via the coalesce helper instead of syn…
ondrejmirtes Jul 6, 2026
a0e64b0
Skip the synthetic constructor-call pricing when the return type cann…
ondrejmirtes Jul 6, 2026
3bf3c14
Answer Scope::getType for a plain variable from scope state, not on d…
ondrejmirtes Jul 6, 2026
42ddab4
Prime the storage with argument results before dynamic function exten…
ondrejmirtes Jul 6, 2026
1432bf6
Prime the storage for method and static call dynamic-return extension…
ondrejmirtes Jul 6, 2026
1c068fa
Revert "Answer Scope::getType for a plain variable from scope state, …
ondrejmirtes Jul 6, 2026
0061b8f
Revert "Skip the synthetic constructor-call pricing when the return t…
ondrejmirtes Jul 6, 2026
fa67aba
Revert "Compose ??= type and narrowing via the coalesce helper instea…
ondrejmirtes Jul 6, 2026
16233dd
Read narrowing subjects' current types from scope state, not on demand
ondrejmirtes Jul 6, 2026
66485ce
Derive non-nullability ensure types from scope state, not on demand
ondrejmirtes Jul 6, 2026
500be3a
Derive narrowing-application base types from scope state, not on demand
ondrejmirtes Jul 6, 2026
46c3762
Keep void in typeCallbacks; project void to null at the value-read bo…
ondrejmirtes Jul 6, 2026
9004eb7
Push the walk's storage onto the scope-visible stack in processStmtNo…
ondrejmirtes Jul 6, 2026
95d0826
Move @phpstan-assert narrowing into DefaultNarrowingHelper, off TypeS…
ondrejmirtes Jul 6, 2026
34b7414
Move conditional-return-type narrowing into DefaultNarrowingHelper, o…
ondrejmirtes Jul 6, 2026
34bd261
Finish the conditional-return-type move: remaining call sites + delet…
ondrejmirtes Jul 6, 2026
4b66947
Gate call-shaped narrowing subjects by reflection instead of walking …
ondrejmirtes Jul 6, 2026
536cab1
Memoize the narrowing-application pricing pair per scope
ondrejmirtes Jul 6, 2026
5add00f
Replace SpecifiedTypes::normalize() with symbolic alternative-form en…
ondrejmirtes Jul 6, 2026
42aaf98
Defer boolean conditional-holder math to the application point
ondrejmirtes Jul 6, 2026
d64418c
Defer the either-branch union recovery to the application point
ondrejmirtes Jul 6, 2026
a6fd2a1
Defer the disjunction holder projection to the application point
ondrejmirtes Jul 6, 2026
e1e21d1
Compute disjunction decided-operand verdicts at the evaluation point
ondrejmirtes Jul 6, 2026
c330e96
Compute comparison, coalesce and nullsafe-twin verdicts at the evalua…
ondrejmirtes Jul 6, 2026
0afee6b
Resolve isset/coalesce issetability verdicts on the evaluation scope
ondrejmirtes Jul 6, 2026
0047099
Derive decomposition branch scopes from the evaluation point, not per…
ondrejmirtes Jul 6, 2026
994e720
Compose nullsafe narrowing from captured results instead of walking a…
ondrejmirtes Jul 6, 2026
14a0ac8
Read isset chain-link types on the evaluation point
ondrejmirtes Jul 6, 2026
ab9358c
Memoize specify results per (context, flavour) at the evaluation point
ondrejmirtes Jul 6, 2026
f203fc3
Flip specifyTypesCallback to (TypeSpecifierContext, bool)
ondrejmirtes Jul 6, 2026
53482f8
Port the coalesce falsey-context gate to the composed narrowing
ondrejmirtes Jul 6, 2026
22b31b4
Resolve boolean-decomposition branch scopes lazily
ondrejmirtes Jul 6, 2026
9c84484
Flip createTypesCallback to (Type, TypeSpecifierContext, bool)
ondrejmirtes Jul 7, 2026
ecb54d2
Read match arm verdicts from the threaded per-arm state
ondrejmirtes Jul 7, 2026
97789ce
Answer foreign-position type asks from the scope's state uniformly
ondrejmirtes Jul 7, 2026
52b759e
Read nullsafe receivers from before the enclosing ensure's device types
ondrejmirtes Jul 7, 2026
4eb33cf
Release each file's captured results; drop dead filterBy* scope caches
ondrejmirtes Jul 7, 2026
5a0ef64
Release resolved typeCallbacks, share the empty specify closure, move…
ondrejmirtes Jul 7, 2026
31560e3
Process function and method bodies against a per-body result storage …
ondrejmirtes Jul 7, 2026
b42c9dc
Read ternary branch results on their processing scopes
ondrejmirtes Jul 7, 2026
de5888f
Expect the flavour-pure native type in the match arm always-true message
ondrejmirtes Jul 7, 2026
5acc289
Revert "Expect the flavour-pure native type in the match arm always-t…
ondrejmirtes Jul 7, 2026
5e6fb4b
Collect branch-union candidates from sureNot narrowings, skip templat…
ondrejmirtes Jul 7, 2026
71a798f
Revert maybe-existing property promotion to its declared type
ondrejmirtes Jul 7, 2026
282bd8e
Bind static to the named class when calling a static method through a…
ondrejmirtes Jul 7, 2026
a9ee7db
Strip the nullsafe short-circuit null from a static call's class-expr…
ondrejmirtes Jul 7, 2026
ff15cfb
Fan receiver-not-null through nullsafe chains from composed create ca…
ondrejmirtes Jul 7, 2026
488354f
Re-price stored results asked at a diverging variable position
ondrejmirtes Jul 7, 2026
872e9b0
Restore per-item array_map expectations lifted by counterfactual re-p…
ondrejmirtes Jul 7, 2026
83c11ad
Process the nullsafe receiver once and let the plain twin consume its…
ondrejmirtes Jul 7, 2026
dbf535b
Exempt closures and arrow functions from the PHPSTAN_GUARD_NW pre-pro…
ondrejmirtes Jul 7, 2026
79ccb9f
Exempt constant shapes and variable reads from the PHPSTAN_GUARD_NW g…
ondrejmirtes Jul 7, 2026
247d224
Sanction deliberate before-the-walk reads under the PHPSTAN_GUARD_NW …
ondrejmirtes Jul 7, 2026
c98b28f
Sanction closure-signature reads of parameter defaults and sibling ca…
ondrejmirtes Jul 7, 2026
5f0f199
Revert the PHPSTAN_GUARD_NW exemptions and sanctioned reads
ondrejmirtes Jul 7, 2026
b0d1337
Carry narrowing subjects' ExpressionResults into SpecifiedTypes
ondrejmirtes Jul 7, 2026
1a2699e
Attach a result to its own narrowing entries as a weak subject reference
ondrejmirtes Jul 7, 2026
2172e36
Consume or cache-price closure arguments in on-demand call re-walks
ondrejmirtes Jul 7, 2026
35eddf1
Skip the synthetic constructor-call pricing when the return type cann…
ondrejmirtes Jul 6, 2026
8c824db
Derive argument-less call subjects' narrowing base from reflection state
ondrejmirtes Jul 7, 2026
37254f7
Revert carrying narrowing subjects' ExpressionResults in SpecifiedTypes
ondrejmirtes Jul 7, 2026
09e5536
Compose ??= from its child results like CoalesceHandler
ondrejmirtes Jul 7, 2026
03a5a02
Store the post-inc/dec synthetic's result before the virtual assign
ondrejmirtes Jul 7, 2026
43074b0
Fabricate virtual assigned-expr results instead of pricing them on de…
ondrejmirtes Jul 7, 2026
23b499d
Add regression test for #14914
ondrejmirtes Jul 7, 2026
5238cce
Add regression test for #14908
ondrejmirtes Jul 7, 2026
ff87aac
Add required lint version comments to bug-14914 and bug-14908 fixtures
ondrejmirtes Jul 7, 2026
db705ae
Read the foreach iteratee off the scope instead of re-walking it
ondrejmirtes Jul 7, 2026
6a85ecb
Compose the foreach non-empty-iteratee narrowing from the iteratee's …
ondrejmirtes Jul 7, 2026
02280c3
Compose switch case narrowing and read match/switch subjects off the …
ondrejmirtes Jul 7, 2026
abf31f6
Read the coalesce left side off the left-is-set scope instead of re-w…
ondrejmirtes Jul 7, 2026
8d857cb
Read the short-ternary condition off its truthy scope instead of re-w…
ondrejmirtes Jul 7, 2026
c605537
Memoize flush-priced results for repeated rule asks
ondrejmirtes Jul 7, 2026
80a35f8
Revert "Memoize flush-priced results for repeated rule asks"
ondrejmirtes Jul 7, 2026
12b2043
Build closure/arrow call-arg types from the single body walk
ondrejmirtes Jul 9, 2026
712a3c1
Select the per-argument metadata acceptor only where its resolution i…
ondrejmirtes Jul 9, 2026
430cb05
Make pass-local storages scope-visible and consume loop condition res…
ondrejmirtes Jul 9, 2026
6dd2842
Memoize the generator check per function-like node
ondrejmirtes Jul 9, 2026
c6a7aff
Answer stored fiber asks without suspending
ondrejmirtes Jul 9, 2026
25b590e
Answer invalidation checks from a per-holder index of contained node …
ondrejmirtes Jul 9, 2026
d4608b7
Memoize flush-priced answers for repeated rule asks
ondrejmirtes Jul 9, 2026
5fd0e7f
Create merge conditionals from the differing holders only
ondrejmirtes Jul 9, 2026
553c3a8
Skip loop verification passes whose entry scope did not change
ondrejmirtes Jul 9, 2026
b7fc37d
Batch type specifications into one scope copy per application
ondrejmirtes Jul 9, 2026
230acf3
Run engine-feeding node-callback gatherers synchronously at the emiss…
ondrejmirtes Jul 9, 2026
21492ff
Align the isset verdict gate with 2.2.x and pin null in falsey isset …
ondrejmirtes Jul 9, 2026
cf3e581
Store the assign-target property fetch's pre-assign result for parked…
ondrejmirtes Jul 9, 2026
525f050
Price closure parameter defaults without a scope walk
ondrejmirtes Jul 9, 2026
c923c62
Create the function call's impure point after its arguments are proce…
ondrejmirtes Jul 9, 2026
e940ad1
Restore the Array_ import dropped when replaying onto the ScopeOps ex…
ondrejmirtes Jul 16, 2026
9d3d921
Update test expectations after the rebase onto 2.2.x
ondrejmirtes Jul 16, 2026
e38aadd
Remove the superseded ScopeOps invalidation helpers after the rebase
ondrejmirtes Jul 16, 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
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;
}

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

namespace PHPStan\Analyser;

use PhpParser\Node\Expr;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_key_exists;

/**
* A deferred description of the boolean-decomposition conditional holders
* (`&&` asserted false, `||` asserted true): the raw narrowing entries of the
* condition side and the holder side, captured where the boolean narrowing was
* composed. The state-dependent math - the condition complements against the
* current type, the holder target types, the vacuity checks - runs in
* evaluate() against the scope the narrowing is applied to
* (MutatingScope::applySpecifiedTypes()), never the scope the composition ran
* on.
*/
final class ConditionalExpressionHolderRecipe
{

/**
* @param list<array{string, Expr, bool, Type}> $conditionEntries [exprString, expr, fromSureTypes, type]
* @param list<array{string, Expr, Type, ?Type}> $holderEntries [exprString, expr, type, target type pinned at compose time (null = read the applying scope)]
*/
public function __construct(
private array $conditionEntries,
private array $holderEntries,
private bool $holdersFromSureTypes,
)
{
}

/**
* @return array<string, ConditionalExpressionHolder[]>
*/
public function evaluate(MutatingScope $scope): array
{
$conditionExpressionTypes = [];
$droppedNoOpConditions = [];
// the unnarrowed type of each condition expression, for the
// dropped-self-condition complement below
$conditionOriginalTypes = [];
foreach ($this->conditionEntries as [$exprString, $expr, $fromSureTypes, $type]) {
$scopeType = $scope->getStateType($expr);
$conditionType = $fromSureTypes
? TypeCombinator::remove($scopeType, $type)
: TypeCombinator::intersect($scopeType, $type);
if ($scopeType->equals($conditionType)) {
$droppedNoOpConditions[$exprString] = true;
continue;
}

$conditionExpressionTypes[$exprString] = ExpressionTypeHolder::createYes($expr, $conditionType);
$conditionOriginalTypes[$exprString] = $scopeType;
}

if ($conditionExpressionTypes === []) {
return [];
}

$holders = [];
foreach ($this->holderEntries as [$exprString, $expr, $type, $pinnedTargetType]) {
// The target's only link to the antecedent was a no-op relation (e.g.
// `$a === $b`) that got dropped, so the antecedent no longer constrains
// it. Projecting a consequent onto it would fire unsoundly. Skip it.
if (array_key_exists($exprString, $droppedNoOpConditions)) {
continue;
}

$conditions = $conditionExpressionTypes;
$droppedSelfCondition = null;
if (isset($conditions[$exprString])) {
$droppedSelfCondition = $conditions[$exprString];
unset($conditions[$exprString]);
}

if ($conditions === []) {
continue;
}

$targetType = $pinnedTargetType ?? $scope->getStateType($expr);
$holderType = $this->holdersFromSureTypes
? TypeCombinator::intersect($targetType, $type)
: TypeCombinator::remove($targetType, $type);

// The dropped self-condition narrowed the target; without it the
// holder must allow the values it excluded, or it over-narrows when
// only the remaining conditions hold. So union back the complement.
if ($droppedSelfCondition !== null) {
$complement = TypeCombinator::remove($conditionOriginalTypes[$exprString], $droppedSelfCondition->getType());
if (!$complement instanceof NeverType) {
$holderType = TypeCombinator::union($holderType, $complement);
}
}

// These boolean-decomposition holders only refine an expression's
// type in a future scope; they must never collapse it to never and
// thereby mark the whole scope unreachable. A never result is an
// artifact (e.g. removing a non-nullable property's full type after
// swapping isset() narrowing), not a real contradiction.
if ($holderType instanceof NeverType && !$targetType instanceof NeverType) {
continue;
}
$holder = new ConditionalExpressionHolder(
$conditions,
ExpressionTypeHolder::createYes($expr, $holderType),
);
$holders[$exprString] ??= [];
$holders[$exprString][$holder->getKey()] = $holder;
}

return $holders;
}

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

namespace PHPStan\Analyser;

/**
* A state-dependent augmentation of a SpecifiedTypes, deferred to the
* application point: MutatingScope::applySpecifiedTypes() evaluates it against
* the applying scope and unions the produced entries into the applied batch.
* The composition captures only position-fixed facts (operand-walk reads);
* everything that must reflect the current state runs in evaluate().
*/
interface DeferredSpecifiedTypesAugment
{

public function evaluate(MutatingScope $scope): ?SpecifiedTypes;

}
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
Loading
Loading