[compiler] Fix false negative: independently memoize values used as method call receivers#35976
Conversation
When a method like `.map()` is called on a value with an unknown type (e.g., the return value of an unknown function), the compiler previously had no function signature available. This caused the fallback in `InferMutationAliasingEffects` to apply `MutateTransitiveConditionally` on the receiver, over-extending its mutable range and merging it into the same reactive scope as the method call result. This meant that `expensiveProcessing(data)` would be re-executed whenever `onClick` changed, even though it only depends on `data`. The fix adds a fallback in `getPropertyType`: when the receiver has an unresolved type (`Type` kind), try to resolve the property from the `BuiltInArray` shape — but only if the method also exists in the `BuiltInMixedReadonly` shape (the safe, non-mutating subset). This gives the compiler access to Array's aliasing signatures which correctly model data flow without over-extending mutable ranges. Fixes facebook#35902
|
Hi @Felipeness! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
josephsavona
left a comment
There was a problem hiding this comment.
This isn’t safe. I believe in commented on a similar PR to this one, but tldr is that if we don’t know the type, then we don’t know that it’s map/filter/etc method is actually non-mutating.
|
Closing since there is no variant of this that works safely. The long-term solution that we’re exploring is using information from type systems (Flow, hopefully also TS) in order to have more information to guide compilation. |
Summary
Fixes #35902
When a method like
.map()is called on a value with an unknown type (e.g. the return value of an unknown function likeexpensiveProcessing(data)), the compiler had no function signature available. This caused the fallback inInferMutationAliasingEffectsto applyMutateTransitiveConditionallyon the receiver, over-extending its mutable range and merging it into the same reactive scope as the method call result.Before (bug):
expensiveProcessing(data)re-executes whenonClickchanges:After (fix):
expensiveProcessing(data)has its own scope depending only ondata:Root cause
In
getPropertyType(), when the receiver has an unresolved type (Typekind), property lookups returnednull— meaning no function signature was available for the method call. Without a signature,InferMutationAliasingEffectsfalls back toMutateTransitiveConditionallyon all operands including the receiver, which extends its mutable range through the method call. This causesInferReactiveScopeVariables(union-find) to group the receiver's creation and the method call into the same reactive scope.Fix
Added a fallback in
getPropertyType: when the receiver has an unresolved type, try to resolve the property from theBuiltInArrayshape — but only if the method also exists inBuiltInMixedReadonly(the safe, non-mutating subset). This follows the same reasoning already documented inObjectShape.tsforBuiltInMixedReadonlyId:We use the
BuiltInArrayshape (notBuiltInMixedReadonly) because it hasaliasingsignatures that correctly model data flow without over-extending mutable ranges. TheMixedReadonlyguard ensures that mutating methods like.push()and.pop()— which only exist inBuiltInArray— are NOT resolved through this fallback, preserving the conservative behavior for those cases.Test plan
bug-independent-memoization-for-unknown-receiver-method-calldemonstrating the fix.push()(mutating, not in MixedReadonly) still uses conservative fallback.filter(),.map()on unknown types now correctly separate scopes