Skip to content

Commit

Permalink
Transitive mixed type, take 2
Browse files Browse the repository at this point in the history
This is a redo of #1640 now that we've established the necessary infrastructure, 
most notably `Effect.ConditionallyMutate` and `noAlias` from #2103 earlier in 
this stack. We can now understand the semantics of hooks that return deeply 
readonly values composed of primitives, arrays, or objects such that any 
`.map()` or `.filter()` calls are guaranteed to be the corresponding array 
methods. That further allows us to refine, since we know that the lambdas passed 
to these calls can't alias, are conditionally mutable, etc. All in all this 
should let us memoize less in practice.
  • Loading branch information
josephsavona committed Sep 18, 2023
1 parent 32569d2 commit 2fa04d1
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 94 deletions.
28 changes: 26 additions & 2 deletions compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
makeIdentifierId,
} from "./HIR";
import {
BuiltInMixedReadonlyId,
DefaultMutatingHook,
DefaultNonmutatingHook,
FunctionSignature,
Expand Down Expand Up @@ -55,6 +56,25 @@ export type Hook = {
* compiler to avoid memoizing arguments.
*/
noAlias?: boolean;

/**
* Specifies whether the hook returns data that is composed of:
* - undefined
* - null
* - boolean
* - number
* - string
* - arrays whose items are also transitiveMixed
* - objects whose values are also transitiveMixed
*
* Many state management and data-fetching APIs return data that meets
* this criteria since this is JSON + undefined. Forget can compile
* hooks that return transitively mixed data more optimally because it
* can make inferences about some method calls (especially array methods
* like `data.items.map(...)` since these builtin types have few built-in
* methods.
*/
transitiveMixedData?: boolean;
};

// TODO(mofeiZ): User defined global types (with corresponding shapes).
Expand Down Expand Up @@ -236,7 +256,9 @@ export class Environment {
addHook(this.#shapes, [], {
positionalParams: [],
restParam: hook.effectKind,
returnType: { kind: "Poly" },
returnType: hook.transitiveMixedData
? { kind: "Object", shapeId: BuiltInMixedReadonlyId }
: { kind: "Poly" },
returnValueKind: hook.valueKind,
calleeEffect: Effect.Read,
hookKind: "Custom",
Expand Down Expand Up @@ -312,7 +334,9 @@ export class Environment {
loc: null,
suggestions: null,
});
return shape.properties.get(property) ?? null;
return (
shape.properties.get(property) ?? shape.properties.get("*") ?? null
);
} else {
return null;
}
Expand Down
37 changes: 37 additions & 0 deletions compiler/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export const BuiltInUseStateId = "BuiltInUseState";
export const BuiltInSetStateId = "BuiltInSetState";
export const BuiltInUseRefId = "BuiltInUseRefId";
export const BuiltInRefValueId = "BuiltInRefValue";
export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly";

/**
* ShapeRegistry with default definitions for built-ins.
Expand Down Expand Up @@ -294,6 +295,42 @@ addObject(BUILTIN_SHAPES, BuiltInUseRefId, [

addObject(BUILTIN_SHAPES, BuiltInRefValueId, []);

addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
[
"toString",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Immutable,
}),
],
[
"map",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
}),
],
[
"filter",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
}),
],
["*", { kind: "Object", shapeId: BuiltInMixedReadonlyId }],
]);

export const DefaultMutatingHook = addHook(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,87 +56,94 @@ function useFragment(_arg1, _arg2) {
}

function Component(props) {
const $ = useMemoCache(16);
const post = useFragment(graphql`...`, props.post);
const c_0 = $[0] !== post;
const $ = useMemoCache(17);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = graphql`...`;
$[0] = t0;
} else {
t0 = $[0];
}
const post = useFragment(t0, props.post);
const c_1 = $[1] !== post;
let media;
let allUrls;
let onClick;
if (c_0) {
if (c_1) {
allUrls = [];

const { media: t0, comments: t2, urls: t4 } = post;
const c_4 = $[4] !== t0;
let t1;
if (c_4) {
t1 = t0 === undefined ? null : t0;
$[4] = t0;
const { media: t1, comments: t3, urls: t5 } = post;
const c_5 = $[5] !== t1;
let t2;
if (c_5) {
t2 = t1 === undefined ? null : t1;
$[5] = t1;
$[6] = t2;
} else {
t1 = $[5];
t2 = $[6];
}
media = t1;
const c_6 = $[6] !== t2;
let t3;
if (c_6) {
t3 = t2 === undefined ? [] : t2;
$[6] = t2;
media = t2;
const c_7 = $[7] !== t3;
let t4;
if (c_7) {
t4 = t3 === undefined ? [] : t3;
$[7] = t3;
$[8] = t4;
} else {
t3 = $[7];
t4 = $[8];
}
const comments = t3;
const c_8 = $[8] !== t4;
let t5;
if (c_8) {
t5 = t4 === undefined ? [] : t4;
$[8] = t4;
const comments = t4;
const c_9 = $[9] !== t5;
let t6;
if (c_9) {
t6 = t5 === undefined ? [] : t5;
$[9] = t5;
$[10] = t6;
} else {
t5 = $[9];
t6 = $[10];
}
const urls = t5;
const c_10 = $[10] !== comments.length;
let t6;
if (c_10) {
t6 = (e) => {
const urls = t6;
const c_11 = $[11] !== comments.length;
let t7;
if (c_11) {
t7 = (e) => {
if (!comments.length) {
return;
}

console.log(comments.length);
};
$[10] = comments.length;
$[11] = t6;
$[11] = comments.length;
$[12] = t7;
} else {
t6 = $[11];
t7 = $[12];
}
onClick = t6;
onClick = t7;

allUrls.push(...urls);
$[0] = post;
$[1] = media;
$[2] = allUrls;
$[3] = onClick;
$[1] = post;
$[2] = media;
$[3] = allUrls;
$[4] = onClick;
} else {
media = $[1];
allUrls = $[2];
onClick = $[3];
media = $[2];
allUrls = $[3];
onClick = $[4];
}
const c_12 = $[12] !== media;
const c_13 = $[13] !== allUrls;
const c_14 = $[14] !== onClick;
let t7;
if (c_12 || c_13 || c_14) {
t7 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
$[12] = media;
$[13] = allUrls;
$[14] = onClick;
$[15] = t7;
const c_13 = $[13] !== media;
const c_14 = $[14] !== allUrls;
const c_15 = $[15] !== onClick;
let t8;
if (c_13 || c_14 || c_15) {
t8 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
$[13] = media;
$[14] = allUrls;
$[15] = onClick;
$[16] = t8;
} else {
t7 = $[15];
t8 = $[16];
}
return t7;
return t8;
}

export const FIXTURE_ENTRYPOINT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,53 +30,60 @@ function Component(props) {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(8);
const post = useFragment(graphql`...`, props.post);
const c_0 = $[0] !== post;
const $ = useMemoCache(9);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = graphql`...`;
$[0] = t0;
} else {
t0 = $[0];
}
const post = useFragment(t0, props.post);
const c_1 = $[1] !== post;
let media;
let onClick;
if (c_0) {
if (c_1) {
const allUrls = [];

const { media: t83, comments, urls } = post;
media = t83;
const c_3 = $[3] !== comments.length;
let t0;
if (c_3) {
t0 = (e) => {
const c_4 = $[4] !== comments.length;
let t1;
if (c_4) {
t1 = (e) => {
if (!comments.length) {
return;
}

console.log(comments.length);
};
$[3] = comments.length;
$[4] = t0;
$[4] = comments.length;
$[5] = t1;
} else {
t0 = $[4];
t1 = $[5];
}
onClick = t0;
onClick = t1;

allUrls.push(...urls);
$[0] = post;
$[1] = media;
$[2] = onClick;
$[1] = post;
$[2] = media;
$[3] = onClick;
} else {
media = $[1];
onClick = $[2];
media = $[2];
onClick = $[3];
}
const c_5 = $[5] !== media;
const c_6 = $[6] !== onClick;
let t1;
if (c_5 || c_6) {
t1 = <Media media={media} onClick={onClick} />;
$[5] = media;
$[6] = onClick;
$[7] = t1;
const c_6 = $[6] !== media;
const c_7 = $[7] !== onClick;
let t2;
if (c_6 || c_7) {
t2 = <Media media={media} onClick={onClick} />;
$[6] = media;
$[7] = onClick;
$[8] = t2;
} else {
t1 = $[7];
t2 = $[8];
}
return t1;
return t2;
}

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,27 @@ function Component(props) {
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const item = useFragment(graphql`...`, props.item);
return item.items?.map((item_0) => renderItem(item_0)) ?? [];
const $ = useMemoCache(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = graphql`...`;
$[0] = t0;
} else {
t0 = $[0];
}
const item = useFragment(t0, props.item);
const c_1 = $[1] !== item.items;
let t1;
if (c_1) {
t1 = item.items?.map((item_0) => renderItem(item_0)) ?? [];
$[1] = item.items;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}

```
Expand Down
Loading

0 comments on commit 2fa04d1

Please sign in to comment.