Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
398 commits
Select commit Hold shift + click to select a range
506af32
Remove todo
ondrejmirtes Jun 24, 2026
5ffa264
Replace handleDefaultTruthyOrFalseyContext with specifyDefaultTypes
ondrejmirtes Jun 24, 2026
7955e83
Read operand/receiver types from their results, not Scope::getType
ondrejmirtes Jun 24, 2026
0e7dc9d
Guard on-demand pricing paths with PHPSTAN_GUARD_NW
ondrejmirtes Jun 24, 2026
a2bcc40
Capture per-arg ExpressionResults; read appending-arg types from them
ondrejmirtes Jun 24, 2026
cda2abc
Read array_splice and resolveReturnType arg types from their results
ondrejmirtes Jun 24, 2026
47192a0
Carry the ??= left side's isset descriptor into its coalesce type
ondrejmirtes Jun 24, 2026
ce5433c
Find the early-terminating expression after processing the statement
ondrejmirtes Jun 24, 2026
99a1301
Consume the processed arg result for the callable-arg check in proces…
ondrejmirtes Jun 24, 2026
7b7767b
Resolve the arrow function return type after its body is processed
ondrejmirtes Jun 24, 2026
2cf4315
Compute Closure::bind's bound scope from its processed arguments
ondrejmirtes Jun 25, 2026
2cbc20f
Fix PHPStan self-analysis errors in NodeScopeResolver
ondrejmirtes Jun 25, 2026
53d5775
Read impossible-check narrowing from the call's ExpressionResult
ondrejmirtes Jun 25, 2026
2658f0a
Gate the processArgs fast path on parameter late-resolvability only
ondrejmirtes Jun 25, 2026
f51644e
Don't route curl_setopt(_array) through the metadata pre-pass
ondrejmirtes Jun 25, 2026
f1a51c5
Don't route implode/join through the metadata pre-pass
ondrejmirtes Jun 25, 2026
a93f391
Supply array_map's callback parameter type via a closure-type extension
ondrejmirtes Jun 25, 2026
f27c892
Supply array_filter/walk/find callback parameter types via closure-ty…
ondrejmirtes Jun 25, 2026
75147b5
Read array_walk's original array type after its argument is processed
ondrejmirtes Jun 25, 2026
2f33a42
Make Closure::bind/bindTo single-pass; drop the empty argsHaveIntrins…
ondrejmirtes Jun 25, 2026
6ce0a4e
Resolve single-variant generic acceptors incrementally so they are si…
ondrejmirtes Jun 25, 2026
f1b5793
Select multi-variant acceptors by argument count so processArgs is si…
ondrejmirtes Jun 25, 2026
4726813
Deduplicate constant-condition vs impossible-check reports via collec…
ondrejmirtes Jun 25, 2026
533de0e
Process a dynamic function-call name before reading its type
ondrejmirtes Jun 25, 2026
ba97452
Process a nested array-dimension before reading its type in AssignHan…
ondrejmirtes Jun 25, 2026
e144684
Process an assign-op Variable/property target as a read before compos…
ondrejmirtes Jun 25, 2026
dd23c89
Process Closure::call's new-$this argument before reading its type
ondrejmirtes Jun 25, 2026
479afb6
Process clone()'s arguments before reading them in clone-with handling
ondrejmirtes Jun 25, 2026
6adaf61
Evaluate an array-dim assignment's value before reading its type
ondrejmirtes Jun 26, 2026
b85d37e
Read the foreach value variable's narrowed type by name in the loop a…
ondrejmirtes Jun 26, 2026
538262e
Carry the nullsafe receiver type to its rule via a virtual node
ondrejmirtes Jun 26, 2026
76304fa
Drop the dead Expr parameter from ExpressionResult's typeCallback
ondrejmirtes Jun 26, 2026
1a16eb0
Build pre-inc/dec literal types via ConstantTypeHelper, not the scope
ondrejmirtes Jun 26, 2026
230c54a
Resolve lexical context once at create()-time in three type callbacks
ondrejmirtes Jun 26, 2026
83b6516
Resolve the instanceof Name class type once at create()-time
ondrejmirtes Jun 26, 2026
9afc589
Reprocess the switch subject for the exhaustiveness check instead of …
ondrejmirtes Jun 26, 2026
1e8d7e8
Reprocess the foreach iteratee and while condition on their narrowed …
ondrejmirtes Jun 26, 2026
eefbeae
Reprocess coalesce/ternary/match subjects on their narrowed scopes in…
ondrejmirtes Jun 26, 2026
e2438a8
Reprocess the foreach iteratee on the post-loop scope for value/key-t…
ondrejmirtes Jun 26, 2026
a2db021
Narrow the pinned-name property fetch via applySpecifiedTypes, not fi…
ondrejmirtes Jun 26, 2026
c4f50bf
Make specifyTypesCallback required and getSpecifiedTypesForScope non-…
ondrejmirtes Jun 26, 2026
b5a2222
Narrow synthetic conditions via processExprOnDemand, not filterByTrut…
ondrejmirtes Jun 26, 2026
7328f75
Read wrapped operand types via getType/getNativeType in cast/clone/un…
ondrejmirtes Jun 27, 2026
e2ad82f
Read child types via getType/getNativeType in post/pre-inc/dec, pipe,…
ondrejmirtes Jun 27, 2026
64ce556
Read the ternary condition type via getType/getNativeType in the type…
ondrejmirtes Jun 27, 2026
929aab5
Propagate the assigned value into byref-intertwined variables instead…
ondrejmirtes Jun 27, 2026
cd68d58
Read binary-op operands via getType/getNativeType in the type callback
ondrejmirtes Jun 27, 2026
feb741b
Read the left operand boolean via getType/getNativeType in boolean ty…
ondrejmirtes Jun 27, 2026
0bce1d5
Resolve property fetch types without the asking scope
ondrejmirtes Jun 27, 2026
0c5792e
Resolve static property fetch types without the asking scope
ondrejmirtes Jun 27, 2026
dbb3eb2
Resolve method call return types without the asking scope
ondrejmirtes Jun 27, 2026
db18047
Resolve static call return types without the asking scope
ondrejmirtes Jun 27, 2026
b35a8b9
Resolve function call return types without the asking scope
ondrejmirtes Jun 27, 2026
43a0aec
Resolve nullsafe fetch/call short-circuit types without the asking scope
ondrejmirtes Jun 27, 2026
a266c14
Read assignment and class-constant-fetch type callbacks via getType/g…
ondrejmirtes Jun 27, 2026
0805622
Read instanceof and coalesce type callbacks without the asking scope
ondrejmirtes Jun 27, 2026
7867e5f
Read assign-op type callback without the asking scope
ondrejmirtes Jun 27, 2026
0fe51ab
Pass the iteratee types into enterForeach instead of re-reading them
ondrejmirtes Jun 27, 2026
4fc2ded
Pass the iteratee types into enterForeachKey instead of re-reading them
ondrejmirtes Jun 27, 2026
92626a8
Make the ExpressionResult typeCallback resolve from a native flag, no…
ondrejmirtes Jun 27, 2026
d5c0638
Require an ExpressionResult to have either a precomputed type or a ty…
ondrejmirtes Jun 27, 2026
20ce071
Read the tracked holder directly in ExpressionResult, not via Scope::…
ondrejmirtes Jun 27, 2026
0d17698
Get rid of truthyScopeCallback and falseyScopeCallback
ondrejmirtes Jun 27, 2026
c2be619
Use the operand's own truthy/falsey scope for &&/|| narrowing
ondrejmirtes Jun 27, 2026
48c05f4
Record conditional holders from by-ref-updated variables via scope state
ondrejmirtes Jun 28, 2026
5426c2a
Derive statement exit points from never instead of findEarlyTerminati…
ondrejmirtes Jun 28, 2026
6f7d1d3
Narrower return type
ondrejmirtes Jun 29, 2026
6453036
Do not use getChildSpecifiedTypes
ondrejmirtes Jun 29, 2026
43474bd
Inline getSpecifiedTypesForScope instead of getChildSpecifiedTypes
ondrejmirtes Jun 30, 2026
58c9133
Keep getType for the dropped-self-condition complement
ondrejmirtes Jun 30, 2026
4745951
Add regression test for the type of a Closure::bind callback
ondrejmirtes Jun 30, 2026
753e6e6
Add regression test for array value type after foreach by-ref reassig…
ondrejmirtes Jun 30, 2026
5cae756
Add regression test for list<non-empty-array> preserved after foreach…
ondrejmirtes Jun 30, 2026
4cc6409
Add regression test for ??= on a dynamic property array offset
ondrejmirtes Jun 30, 2026
431057e
Resolve ExpressionResult types from memoized per-flavour slots
ondrejmirtes Jul 2, 2026
d370184
Split readStoredOrPriceOnDemand into decisive primitives
ondrejmirtes Jul 2, 2026
81f4161
Reintroduce readStoredResult as a storage-based assertion
ondrejmirtes Jul 2, 2026
fdbd816
Narrow ?? by composing isset facts from chain results, not a syntheti…
ondrejmirtes Jul 2, 2026
1671f68
Compose multi-subject isset() truthy narrowing from chain results
ondrejmirtes Jul 2, 2026
0b17113
Consume the match arms' captured scope/type pairs instead of re-walki…
ondrejmirtes Jul 2, 2026
7547bd9
Answer plain variable reads from scope state instead of on-demand pro…
ondrejmirtes Jul 2, 2026
628a096
Run AnalyserTest through FiberNodeScopeResolver like the other test h…
ondrejmirtes Jul 2, 2026
6bc23c9
Resolve the offset-write property holder without re-reading the receiver
ondrejmirtes Jul 2, 2026
74223b3
Answer maybe-stored reads from a tracked whole-expression holder with…
ondrejmirtes Jul 2, 2026
7faae80
Skip the null-containment probe for bare variable narrowing subjects
ondrejmirtes Jul 3, 2026
622adb8
Pin the dropped-self-condition complement type at holder-build time
ondrejmirtes Jul 3, 2026
67a949d
Compose createSubjectTypes entries from the result's own facts, never…
ondrejmirtes Jul 3, 2026
5537980
Collapse filterByTruthyValue/filterByFalseyValue onto applySpecifiedT…
ondrejmirtes Jul 3, 2026
3ab080e
Narrow match scopes via applySpecifiedTypes instead of filterBy*
ondrejmirtes Jul 3, 2026
d890262
Compute call narrowing on the evaluation scope, flavoured by the aski…
ondrejmirtes Jul 4, 2026
e77c30e
Compose null-identity narrowing from operand results in IdenticalNarr…
ondrejmirtes Jul 4, 2026
e430fc2
Narrow bool-literal identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
e99bb5f
Narrow scalar-literal identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
0d14bb4
Compose array_key_first-family null narrowing through the FuncCall co…
ondrejmirtes Jul 4, 2026
f2ab758
Narrow get_class/get_debug_type identity comparisons in IdenticalNarr…
ondrejmirtes Jul 4, 2026
827b2b1
Narrow gettype identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
bfd291d
Narrow preg_match identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 4, 2026
e5b7abc
Narrow strlen/mb_strlen identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
3a2587f
Move count-size narrowing into CountNarrowingHelper
ondrejmirtes Jul 5, 2026
688cf49
Narrow count/sizeof identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
2d7eae6
Narrow string-function identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
36f850e
Narrow trim and get_parent_class identity comparisons in IdenticalNar…
ondrejmirtes Jul 5, 2026
18451ca
Narrow ::class identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
f98ad49
Narrow general identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
a677979
Compose function-family narrowing for type-based constant sides
ondrejmirtes Jul 5, 2026
14c023e
Narrow loose-equality comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
f09327e
Compose assign-time conditional holders from the assigned expression'…
ondrejmirtes Jul 5, 2026
b9ea917
Read assign-time ternary holder types from the captured walk results
ondrejmirtes Jul 5, 2026
502b76e
Extract conjunction narrowing into BooleanNarrowingHelper
ondrejmirtes Jul 5, 2026
f4126d0
Fold multi-subject isset narrowing through the conjunction helper
ondrejmirtes Jul 5, 2026
07c72a5
Extract disjunction narrowing into BooleanNarrowingHelper
ondrejmirtes Jul 5, 2026
91b175b
Share the single-subject isset narrowing in DefaultNarrowingHelper
ondrejmirtes Jul 5, 2026
42b7bb7
Compose empty() narrowing through the disjunction helper
ondrejmirtes Jul 5, 2026
647518e
Compose ternary narrowing through the boolean helpers
ondrejmirtes Jul 5, 2026
f81dbb3
Compose cast narrowing through the loose-equality helper
ondrejmirtes Jul 5, 2026
2575954
Read tracked foreach and @var types without synthetic pricing
ondrejmirtes Jul 5, 2026
06f99d6
Capture call-argument results for the composed comparison narrowing
ondrejmirtes Jul 5, 2026
e51905a
Narrow bool-typed identity comparisons in IdenticalNarrowingHelper
ondrejmirtes Jul 5, 2026
17dbe3a
Merge both directions of call-vs-call identity comparisons
ondrejmirtes Jul 5, 2026
927e596
Compose the last identity-comparison tails
ondrejmirtes Jul 5, 2026
d048f74
Delete the old-world equality narrowing
ondrejmirtes Jul 5, 2026
75fd357
Key the closure type cache by the closure's actual inputs
ondrejmirtes Jul 5, 2026
dd997a3
Read assign sentinel base result from the walk storage
ondrejmirtes Jul 5, 2026
87fd5cb
Compose comparison verdicts from captured results instead of re-walking
ondrejmirtes Jul 5, 2026
9082c69
Compose nullsafe method call null-removal from the receiver's stored …
ondrejmirtes Jul 5, 2026
5e41267
Compose nullsafe property fetch null-removal from the receiver's stor…
ondrejmirtes Jul 5, 2026
d559ea5
Compose variable-variable narrowing from the name expression's result
ondrejmirtes Jul 5, 2026
252c702
Read by-ref destructuring item types from the scope directly
ondrejmirtes Jul 5, 2026
8c8da70
Compose match arm condition verdicts and narrowing from walk results
ondrejmirtes Jul 5, 2026
0625324
Use default narrowing directly for bool-variable contradiction probing
ondrejmirtes Jul 5, 2026
dda3704
Compose single-condition match arm filtering verdicts from walk results
ondrejmirtes Jul 6, 2026
8de5682
Reuse the ensured-scope call result for the nullsafe maybe-null type
ondrejmirtes Jul 6, 2026
7e66b20
Extract the coalesce type and falsey-narrowing composition into a helper
ondrejmirtes Jul 6, 2026
61f9fce
Compose ??= type and narrowing via the coalesce helper instead of syn…
ondrejmirtes Jul 6, 2026
d417402
Skip the synthetic constructor-call pricing when the return type cann…
ondrejmirtes Jul 6, 2026
1eea5df
Answer Scope::getType for a plain variable from scope state, not on d…
ondrejmirtes Jul 6, 2026
ea0415c
Prime the storage with argument results before dynamic function exten…
ondrejmirtes Jul 6, 2026
2a24716
Prime the storage for method and static call dynamic-return extension…
ondrejmirtes Jul 6, 2026
639e67f
Revert "Answer Scope::getType for a plain variable from scope state, …
ondrejmirtes Jul 6, 2026
9e340e3
Revert "Skip the synthetic constructor-call pricing when the return t…
ondrejmirtes Jul 6, 2026
f1fc044
Revert "Compose ??= type and narrowing via the coalesce helper instea…
ondrejmirtes Jul 6, 2026
b1b632a
Read narrowing subjects' current types from scope state, not on demand
ondrejmirtes Jul 6, 2026
a11f9db
Derive non-nullability ensure types from scope state, not on demand
ondrejmirtes Jul 6, 2026
b8deae1
Derive narrowing-application base types from scope state, not on demand
ondrejmirtes Jul 6, 2026
bca8895
Keep void in typeCallbacks; project void to null at the value-read bo…
ondrejmirtes Jul 6, 2026
49885bb
Push the walk's storage onto the scope-visible stack in processStmtNo…
ondrejmirtes Jul 6, 2026
91efd1a
Move @phpstan-assert narrowing into DefaultNarrowingHelper, off TypeS…
ondrejmirtes Jul 6, 2026
9df1dd3
Move conditional-return-type narrowing into DefaultNarrowingHelper, o…
ondrejmirtes Jul 6, 2026
c4d7c48
Finish the conditional-return-type move: remaining call sites + delet…
ondrejmirtes Jul 6, 2026
57905dd
Gate call-shaped narrowing subjects by reflection instead of walking …
ondrejmirtes Jul 6, 2026
a0f7b94
Memoize the narrowing-application pricing pair per scope
ondrejmirtes Jul 6, 2026
c41cd39
Replace SpecifiedTypes::normalize() with symbolic alternative-form en…
ondrejmirtes Jul 6, 2026
5295a36
Defer boolean conditional-holder math to the application point
ondrejmirtes Jul 6, 2026
57ba793
Defer the either-branch union recovery to the application point
ondrejmirtes Jul 6, 2026
a59bc6f
Defer the disjunction holder projection to the application point
ondrejmirtes Jul 6, 2026
3a3b1eb
Compute disjunction decided-operand verdicts at the evaluation point
ondrejmirtes Jul 6, 2026
f420857
Compute comparison, coalesce and nullsafe-twin verdicts at the evalua…
ondrejmirtes Jul 6, 2026
39d9eba
Resolve isset/coalesce issetability verdicts on the evaluation scope
ondrejmirtes Jul 6, 2026
6c8d2cf
Derive decomposition branch scopes from the evaluation point, not per…
ondrejmirtes Jul 6, 2026
68c9480
Compose nullsafe narrowing from captured results instead of walking a…
ondrejmirtes Jul 6, 2026
527fa1c
Read isset chain-link types on the evaluation point
ondrejmirtes Jul 6, 2026
60d8c3c
Memoize specify results per (context, flavour) at the evaluation point
ondrejmirtes Jul 6, 2026
302aa31
Flip specifyTypesCallback to (TypeSpecifierContext, bool)
ondrejmirtes Jul 6, 2026
4bcf069
Port the coalesce falsey-context gate to the composed narrowing
ondrejmirtes Jul 6, 2026
ab4b1eb
Resolve boolean-decomposition branch scopes lazily
ondrejmirtes Jul 6, 2026
f09be29
Flip createTypesCallback to (Type, TypeSpecifierContext, bool)
ondrejmirtes Jul 7, 2026
2e8b20f
Read match arm verdicts from the threaded per-arm state
ondrejmirtes Jul 7, 2026
a51a02d
Answer foreign-position type asks from the scope's state uniformly
ondrejmirtes Jul 7, 2026
1d2ceb5
Read nullsafe receivers from before the enclosing ensure's device types
ondrejmirtes Jul 7, 2026
996d937
Release each file's captured results; drop dead filterBy* scope caches
ondrejmirtes Jul 7, 2026
a464c5a
Release resolved typeCallbacks, share the empty specify closure, move…
ondrejmirtes Jul 7, 2026
6311fe1
Process function and method bodies against a per-body result storage …
ondrejmirtes Jul 7, 2026
27468a8
Read ternary branch results on their processing scopes
ondrejmirtes Jul 7, 2026
923d86f
Expect the flavour-pure native type in the match arm always-true message
ondrejmirtes Jul 7, 2026
730fac3
Revert "Expect the flavour-pure native type in the match arm always-t…
ondrejmirtes Jul 7, 2026
3f594b9
Collect branch-union candidates from sureNot narrowings, skip templat…
ondrejmirtes Jul 7, 2026
9e8033b
Revert maybe-existing property promotion to its declared type
ondrejmirtes Jul 7, 2026
494687c
Bind static to the named class when calling a static method through a…
ondrejmirtes Jul 7, 2026
567a6a2
Strip the nullsafe short-circuit null from a static call's class-expr…
ondrejmirtes Jul 7, 2026
08c9299
Fan receiver-not-null through nullsafe chains from composed create ca…
ondrejmirtes Jul 7, 2026
f0e23f6
Re-price stored results asked at a diverging variable position
ondrejmirtes Jul 7, 2026
bd042db
Restore per-item array_map expectations lifted by counterfactual re-p…
ondrejmirtes Jul 7, 2026
8c1a2a6
Process the nullsafe receiver once and let the plain twin consume its…
ondrejmirtes Jul 7, 2026
2a16bc5
Exempt closures and arrow functions from the PHPSTAN_GUARD_NW pre-pro…
ondrejmirtes Jul 7, 2026
f0c2e47
Exempt constant shapes and variable reads from the PHPSTAN_GUARD_NW g…
ondrejmirtes Jul 7, 2026
5f442f1
Sanction deliberate before-the-walk reads under the PHPSTAN_GUARD_NW …
ondrejmirtes Jul 7, 2026
031b9a3
Sanction closure-signature reads of parameter defaults and sibling ca…
ondrejmirtes Jul 7, 2026
c2555a6
Revert the PHPSTAN_GUARD_NW exemptions and sanctioned reads
ondrejmirtes Jul 7, 2026
38e5e10
Carry narrowing subjects' ExpressionResults into SpecifiedTypes
ondrejmirtes Jul 7, 2026
d5fa1aa
Attach a result to its own narrowing entries as a weak subject reference
ondrejmirtes Jul 7, 2026
4e8b476
Consume or cache-price closure arguments in on-demand call re-walks
ondrejmirtes Jul 7, 2026
661abf9
Skip the synthetic constructor-call pricing when the return type cann…
ondrejmirtes Jul 6, 2026
7c02dfe
Derive argument-less call subjects' narrowing base from reflection state
ondrejmirtes Jul 7, 2026
088a2ed
Revert carrying narrowing subjects' ExpressionResults in SpecifiedTypes
ondrejmirtes Jul 7, 2026
dc96cc7
Compose ??= from its child results like CoalesceHandler
ondrejmirtes Jul 7, 2026
d3059f8
Store the post-inc/dec synthetic's result before the virtual assign
ondrejmirtes Jul 7, 2026
fe10f22
Fabricate virtual assigned-expr results instead of pricing them on de…
ondrejmirtes Jul 7, 2026
c12d774
Add regression test for #14914
ondrejmirtes Jul 7, 2026
2b580a7
Add regression test for #14908
ondrejmirtes Jul 7, 2026
079a9e9
Add required lint version comments to bug-14914 and bug-14908 fixtures
ondrejmirtes Jul 7, 2026
0eaa19f
Read the foreach iteratee off the scope instead of re-walking it
ondrejmirtes Jul 7, 2026
8ea3f09
Compose the foreach non-empty-iteratee narrowing from the iteratee's …
ondrejmirtes Jul 7, 2026
9c2f6b5
Compose switch case narrowing and read match/switch subjects off the …
ondrejmirtes Jul 7, 2026
d83ce99
Read the coalesce left side off the left-is-set scope instead of re-w…
ondrejmirtes Jul 7, 2026
5dd3828
Read the short-ternary condition off its truthy scope instead of re-w…
ondrejmirtes Jul 7, 2026
f079559
Memoize flush-priced results for repeated rule asks
ondrejmirtes Jul 7, 2026
0b315ee
Revert "Memoize flush-priced results for repeated rule asks"
ondrejmirtes Jul 7, 2026
ffd629c
Build closure/arrow call-arg types from the single body walk
ondrejmirtes Jul 9, 2026
8e4a823
Select the per-argument metadata acceptor only where its resolution i…
ondrejmirtes Jul 9, 2026
fd56e11
Make pass-local storages scope-visible and consume loop condition res…
ondrejmirtes Jul 9, 2026
d390684
Memoize the generator check per function-like node
ondrejmirtes Jul 9, 2026
80d5a2d
Answer stored fiber asks without suspending
ondrejmirtes Jul 9, 2026
34b28c2
Answer invalidation checks from a per-holder index of contained node …
ondrejmirtes Jul 9, 2026
15a87a1
Memoize flush-priced answers for repeated rule asks
ondrejmirtes Jul 9, 2026
eddfbdb
Create merge conditionals from the differing holders only
ondrejmirtes Jul 9, 2026
30a8579
Skip loop verification passes whose entry scope did not change
ondrejmirtes Jul 9, 2026
d19c78b
Batch type specifications into one scope copy per application
ondrejmirtes Jul 9, 2026
96f4624
Run engine-feeding node-callback gatherers synchronously at the emiss…
ondrejmirtes Jul 9, 2026
28d8f86
Align the isset verdict gate with 2.2.x and pin null in falsey isset …
ondrejmirtes Jul 9, 2026
0cf8570
Store the assign-target property fetch's pre-assign result for parked…
ondrejmirtes Jul 9, 2026
9d299b4
Price closure parameter defaults without a scope walk
ondrejmirtes Jul 9, 2026
dd94fe7
Create the function call's impure point after its arguments are proce…
ondrejmirtes Jul 9, 2026
4f28cc3
Restore the Array_ import dropped when replaying onto the ScopeOps ex…
ondrejmirtes Jul 16, 2026
886f53c
Update test expectations after the rebase onto 2.2.x
ondrejmirtes Jul 16, 2026
8603959
Remove the superseded ScopeOps invalidation helpers after the rebase
ondrejmirtes Jul 16, 2026
1622bd9
Answer invalidation checks from ScopeOps again
ondrejmirtes Jul 16, 2026
e07981a
Sync the turbo native twins with the single-pass engine's shadowed cl…
ondrejmirtes Jul 16, 2026
5cc1a86
Pin turbo extension version to cac4327
ondrejmirtes Jul 16, 2026
49c7e55
Resolve the call return type once per call
ondrejmirtes Jul 16, 2026
abbbe23
Complete the stored preliminary call result in place instead of overw…
ondrejmirtes Jul 16, 2026
7072851
Walk closure by-ref convergence passes in deep statement context
ondrejmirtes Jul 16, 2026
d3c1799
Drop unnecessary ?? null on always-set parameter types
ondrejmirtes Jul 22, 2026
eef3194
Drop the buildTypeSpecifications smoke block for the removed helper
ondrejmirtes Jul 22, 2026
11a6f0e
Fix use-statement order left by the rebase auto-merge
ondrejmirtes Jul 22, 2026
91ed434
Price an offset read on never as ErrorType in the narrowing-base reader
ondrejmirtes Jul 28, 2026
a1e3289
Avoid the nullsafe operator - simple-downgrader passes it through
ondrejmirtes Jul 28, 2026
698e5e1
Use ExtensionsCollection in ExpressionResult and tag PerFileAnalysisR…
ondrejmirtes Jul 28, 2026
6777b6f
Construct name checks from extensions collections in ClassConstantPhp…
ondrejmirtes Jul 28, 2026
78d3480
Bump expected turbo version
ondrejmirtes Jul 28, 2026
0c28cc4
Restore the default-value gate in the isset verdict
ondrejmirtes Jul 28, 2026
1a1dcfd
Complete the callable-union parameter merge in createCallableParameters
ondrejmirtes Jul 28, 2026
ea67c11
Bump expected turbo version
ondrejmirtes Jul 28, 2026
a061278
Absorb the upstream TypeSpecifierTest additions after the rebase
ondrejmirtes Jul 28, 2026
274f320
Drop the upstream toSureTypes copy that flowed into the holder helper
ondrejmirtes Jul 28, 2026
8495201
Bump expected turbo version
ondrejmirtes Jul 28, 2026
a834c78
Drop the upstream duplicate of the differingKeys smoke block
ondrejmirtes Jul 28, 2026
e685f86
Split processAssignVar into prepareTarget and applyWrite
ondrejmirtes Jul 28, 2026
c789b9b
Produce the assignment target's read result inside prepareTarget
ondrejmirtes Jul 28, 2026
a7280a3
Pass the assigned value's result into applyWrite instead of pre-stori…
ondrejmirtes Jul 28, 2026
d0df9ff
Compose the ??= read from the walked target instead of a second walk
ondrejmirtes Jul 29, 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: 2
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;
}

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

namespace PHPStan\Analyser;

/**
* How AssignHandler::prepareTarget() walks the assignment target.
*
* Besides whether target sub-expressions are walked inside
* enterExpressionAssign() scopes, the mode says whether the walk also prices
* the whole target as a read: `$lvalue OP= ...` reads the old value of
* `$lvalue`, and `$lvalue ??= ...` reads it with isset() semantics (no
* undefined-variable/uninitialized-property reports; the read carries the
* isset descriptor). The read happens inside the one target walk instead of
* callers re-processing the target with a noop callback.
*
* @internal
*/
final class AssignTargetWalkMode
{

private function __construct(
private bool $enterExpressionAssign,
private bool $producesTargetReadResult,
private bool $issetSemanticsForRead,
)
{
}

public static function assign(): self
{
return new self(true, false, false);
}

public static function virtualAssign(): self
{
return new self(false, false, false);
}

public static function readModifyWrite(): self
{
return new self(false, true, false);
}

public static function coalesceReadModifyWrite(): self
{
return new self(true, true, true);
}

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

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

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

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

}
Loading