diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
index 901dfd6ff6..676e9ecd0b 100644
--- a/compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+++ b/compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
@@ -17,6 +17,7 @@ import {
DEFAULT_SHAPES,
Global,
GlobalRegistry,
+ installReAnimatedTypes,
} from "./Globals";
import {
BlockId,
@@ -357,6 +358,16 @@ const EnvironmentConfigSchema = z.object({
*/
enableTreatFunctionDepsAsConditional: z.boolean().default(false),
+ /**
+ * The react native re-animated library uses custom Babel transforms that
+ * requires the calls to library API remain unmodified.
+ *
+ * If this flag is turned on, the React compiler will use custom type
+ * definitions for reanimated library to make it's Babel plugin work
+ * with the compiler.
+ */
+ enableCustomTypeDefinitionForReAnimated: z.boolean().default(false),
+
/**
* If specified, this value is used as a pattern for determing which global values should be
* treated as hooks. The pattern should have a single capture group, which will be used as
@@ -469,6 +480,10 @@ export class Environment {
);
}
+ if (config.enableCustomTypeDefinitionForReAnimated) {
+ installReAnimatedTypes(this.#globals, this.#shapes);
+ }
+
this.#contextIdentifiers = contextIdentifiers;
this.#hoistedIdentifiers = new Set();
}
diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
index 899bb64108..2679833c0b 100644
--- a/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
+++ b/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
@@ -9,6 +9,7 @@ import { Effect, ValueKind, ValueReason } from "./HIR";
import {
BUILTIN_SHAPES,
BuiltInArrayId,
+ BuiltInMixedReadonlyId,
BuiltInUseEffectHookId,
BuiltInUseInsertionEffectHookId,
BuiltInUseLayoutEffectHookId,
@@ -413,3 +414,76 @@ DEFAULT_GLOBALS.set(
"globalThis",
addObject(DEFAULT_SHAPES, "globalThis", TYPED_GLOBALS)
);
+
+export function installReAnimatedTypes(
+ globals: GlobalRegistry,
+ registry: ShapeRegistry
+): void {
+ // hooks that freeze args and return frozen value
+ const frozenHooks = [
+ "useFrameCallback",
+ "useAnimatedStyle",
+ "useAnimatedProps",
+ "useAnimatedScrollHandler",
+ "useAnimatedReaction",
+ "useWorkletCallback",
+ ];
+ for (const hook of frozenHooks) {
+ globals.set(
+ hook,
+ addHook(registry, {
+ positionalParams: [],
+ restParam: Effect.Freeze,
+ returnType: { kind: "Object", shapeId: BuiltInMixedReadonlyId },
+ returnValueKind: ValueKind.Frozen,
+ noAlias: true,
+ calleeEffect: Effect.Read,
+ hookKind: "Custom",
+ })
+ );
+ }
+
+ /**
+ * hooks that return a mutable value. ideally these should be modelled as a
+ * ref, but this works for now.
+ */
+ const mutableHooks = ["useSharedValue", "useDerivedValue"];
+ for (const hook of mutableHooks) {
+ globals.set(
+ hook,
+ addHook(registry, {
+ positionalParams: [],
+ restParam: Effect.Freeze,
+ returnType: { kind: "Poly" },
+ returnValueKind: ValueKind.Mutable,
+ noAlias: true,
+ calleeEffect: Effect.Read,
+ hookKind: "Custom",
+ })
+ );
+ }
+
+ // functions that return mutable value
+ const funcs = [
+ "withTiming",
+ "withSpring",
+ "createAnimatedPropAdapter",
+ "withDecay",
+ "withRepeat",
+ "runOnUI",
+ "executeOnUIRuntimeSync",
+ ];
+ for (const fn of funcs) {
+ globals.set(
+ fn,
+ addFunction(registry, [], {
+ positionalParams: [],
+ restParam: Effect.Read,
+ returnType: { kind: "Poly" },
+ calleeEffect: Effect.Read,
+ returnValueKind: ValueKind.Mutable,
+ noAlias: true,
+ })
+ );
+ }
+}
diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reanimated-no-memo-arg.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reanimated-no-memo-arg.expect.md
new file mode 100644
index 0000000000..94e4ba4d07
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reanimated-no-memo-arg.expect.md
@@ -0,0 +1,76 @@
+
+## Input
+
+```javascript
+// @enableCustomTypeDefinitionForReAnimated
+function Component() {
+ const radius = useSharedValue(50);
+
+ const animatedProps = useAnimatedProps(() => {
+ // draw a circle
+ const path = `
+ M 100, 100
+ m -${radius.value}, 0
+ a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
+ a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
+ `;
+ return {
+ d: path,
+ };
+ });
+
+ // attach animated props to an SVG path using animatedProps
+ return (
+
+ );
+}
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [],
+ isComponent: false,
+};
+
+```
+
+## Code
+
+```javascript
+import { unstable_useMemoCache as useMemoCache } from "react"; // @enableCustomTypeDefinitionForReAnimated
+function Component() {
+ const $ = useMemoCache(2);
+ const radius = useSharedValue(50);
+
+ const animatedProps = useAnimatedProps(() => {
+ const path = `
+ M 100, 100
+ m -${radius.value}, 0
+ a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
+ a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
+ `;
+ return { d: path };
+ });
+ let t0;
+ if ($[0] !== animatedProps) {
+ t0 = (
+
+ );
+ $[0] = animatedProps;
+ $[1] = t0;
+ } else {
+ t0 = $[1];
+ }
+ return t0;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [],
+ isComponent: false,
+};
+
+```
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reanimated-no-memo-arg.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reanimated-no-memo-arg.js
new file mode 100644
index 0000000000..eca548f748
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reanimated-no-memo-arg.js
@@ -0,0 +1,29 @@
+// @enableCustomTypeDefinitionForReAnimated
+function Component() {
+ const radius = useSharedValue(50);
+
+ const animatedProps = useAnimatedProps(() => {
+ // draw a circle
+ const path = `
+ M 100, 100
+ m -${radius.value}, 0
+ a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
+ a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
+ `;
+ return {
+ d: path,
+ };
+ });
+
+ // attach animated props to an SVG path using animatedProps
+ return (
+
+ );
+}
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [],
+ isComponent: false,
+};
diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts
index fef339c363..2faaf15443 100644
--- a/compiler/packages/snap/src/SproutTodoFilter.ts
+++ b/compiler/packages/snap/src/SproutTodoFilter.ts
@@ -490,6 +490,7 @@ const skipFilter = new Set([
"fbt/fbt-preserve-jsxtext",
"todo.useContext-mutate-context-in-callback",
"loop-unused-let",
+ "reanimated-no-memo-arg",
// Tested e2e in forget-feedback repo
"userspace-use-memo-cache",