-
Notifications
You must be signed in to change notification settings - Fork 46.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[compiler] First cut at dep inference #31386
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Ahh I think what you might be observing is that we create scopes for hook calls, but then flatten them away. And so either you’re looking at the pre-flattened state or you’re looking at a later pass after we flatten to a labeled block. We want to get rid of the labeled block and truly flatten it as if there had never been a scope, though. So I would definitely code as if there could be other instructions in the block w the effect. |
No, that should only happen if there wasn’t an inline function expression. |
/** | ||
* Enables inference of effect dependencies. Still experimental. | ||
*/ | ||
EXPERIMENTAL_inferEffectDependencies: z.boolean().default(false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we haven't been doing "experimental" etc for feature flags. If it's off by default, it's experimental.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exciting! A bunch of smaller feedback, we should discuss the strategy (given the need for guaranteed compilation) in our compiler design sync
import { ArrayExpression, Effect, Environment, FunctionExpression, GeneratedSource, HIRFunction, IdentifierId, Instruction, isUseEffectHookType, makeInstructionId } from "../HIR"; | ||
import { createTemporaryPlace } from "../HIR/HIRBuilder"; | ||
|
||
export function inferEffectDependencies( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docblock
} | ||
|
||
for (const [, block] of fn.body.blocks) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: combine these loops, you're guaranteed to see a function expression before its usage
let newInstructions = [...block.instructions]; | ||
let addedInstrs = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's do the pattern used elsewhere where we only allocate a new array if there's actually something to change. most blocks won't have effects.
value.args[0].kind === 'Identifier' && | ||
value.args.length === 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is TypeScript's unsoundness peeking through (ie it assumes array indexing is non-nullable) - these checks need to be reordered, the first could fail if there are no args
if (fnExpr != null) { | ||
const deps: ArrayExpression = { | ||
kind: "ArrayExpression", | ||
elements: [...fnExpr.loweredFunc.dependencies], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we probably want to filter non-reactive deps here
useEffect(() => { | ||
console.log(foo); | ||
console.log(bar?.baz); | ||
console.log(bar.qux); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a reference to a global, and a local variable with a non-reactive value? they should be part of the function deps but not added as effect deps
This is for researching/prototyping, not a feature we are releasing imminently.
Putting up an early version of inferring effect dependencies to get feedback on the approach. We do not plan to ship this as-is, and may not start by going after direct
useEffect
calls. Until we make that decision, the heuristic I use to detect when to insert effect deps will suffice for testing.The approach is simple: when we see a useEffect call with no dep array we insert the deps inferred for the lambda passed in. If the first argument is not a lambda then we do not do anything.
This diff is the easy part. I think the harder part will be ensuring that we can infer the deps even when we have to bail out of memoization. We have no other features that must run regardless of rules of react violations. Does anyone foresee any issues using the compiler passes to infer reactive deps when there may be violations?
I have a few questions:
addedInstrs
variable that I use to make sure I insert the effect deps array temp creation at the right spot.