Skip to content

Commit

Permalink
[compiler] Special-case phi inference for mixed readonly type
Browse files Browse the repository at this point in the history
This allows us to handle common operations such as `useFragment(...).edges.nodes ?? []` where we have a `Phi(MixedReadonly, Array)`. The underlying pattern remains general-purpose and not Relay-specific, and any API that returns transitively "mixed" data (primitives, arrays, plain objects) can benefit from the same type refinement.

ghstack-source-id: 51283108942002a14d032613a9d0b8b665ee3a94
Pull Request resolved: #30797
  • Loading branch information
josephsavona committed Aug 23, 2024
1 parent 4f54674 commit 1b74782
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
BuiltInArrayId,
BuiltInFunctionId,
BuiltInJsxId,
BuiltInMixedReadonlyId,
BuiltInObjectId,
BuiltInPropsId,
BuiltInRefValueId,
Expand Down Expand Up @@ -496,8 +497,13 @@ class Unifier {
if (candidateType === null) {
candidateType = resolved;
} else if (!typeEquals(resolved, candidateType)) {
candidateType = null;
break;
const unionType = tryUnionTypes(resolved, candidateType);
if (unionType === null) {
candidateType = null;
break;
} else {
candidateType = unionType;
}
} // else same type, continue
}

Expand Down Expand Up @@ -650,3 +656,39 @@ const RefLikeNameRE = /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/;
function isRefLikeName(t: PropType): boolean {
return RefLikeNameRE.test(t.objectName) && t.propertyName === 'current';
}

function tryUnionTypes(ty1: Type, ty2: Type): Type | null {
let readonlyType: Type;
let otherType: Type;
if (ty1.kind === 'Object' && ty1.shapeId === BuiltInMixedReadonlyId) {
readonlyType = ty1;
otherType = ty2;
} else if (ty2.kind === 'Object' && ty2.shapeId === BuiltInMixedReadonlyId) {
readonlyType = ty2;
otherType = ty1;
} else {
return null;
}
if (otherType.kind === 'Primitive') {
/**
* Union(Primitive | MixedReadonly) = MixedReadonly
*
* For example, `data ?? null` could return `data`, the fact that RHS
* is a primitive doesn't guarantee the result is a primitive.
*/
return readonlyType;
} else if (
otherType.kind === 'Object' &&
otherType.shapeId === BuiltInArrayId
) {
/**
* Union(Array | MixedReadonly) = Array
*
* In practice this pattern means the result is always an array. Given
* that this behavior requires opting-in to the mixedreadonly type
* (via moduleTypeProvider) this seems like a reasonable heuristic.
*/
return otherType;
}
return null;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

## Input

```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {useFragment} from 'shared-runtime';

function Component() {
const data = useFragment();
const nodes = data.nodes ?? [];
const flatMap = nodes.flatMap(node => node.items);
const filtered = flatMap.filter(item => item != null);
const map = useMemo(() => filtered.map(), [filtered]);
const index = filtered.findIndex(x => x === null);

return (
<div>
{map}
{index}
</div>
);
}

```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import { useFragment } from "shared-runtime";

function Component() {
const $ = _c(11);
const data = useFragment();
let t0;
if ($[0] !== data.nodes) {
t0 = data.nodes ?? [];
$[0] = data.nodes;
$[1] = t0;
} else {
t0 = $[1];
}
const nodes = t0;
let t1;
if ($[2] !== nodes) {
t1 = nodes.flatMap(_temp);
$[2] = nodes;
$[3] = t1;
} else {
t1 = $[3];
}
const flatMap = t1;
let t2;
if ($[4] !== flatMap) {
t2 = flatMap.filter(_temp2);
$[4] = flatMap;
$[5] = t2;
} else {
t2 = $[5];
}
const filtered = t2;
let t3;
let t4;
if ($[6] !== filtered) {
t4 = filtered.map();
$[6] = filtered;
$[7] = t4;
} else {
t4 = $[7];
}
t3 = t4;
const map = t3;
const index = filtered.findIndex(_temp3);
let t5;
if ($[8] !== map || $[9] !== index) {
t5 = (
<div>
{map}
{index}
</div>
);
$[8] = map;
$[9] = index;
$[10] = t5;
} else {
t5 = $[10];
}
return t5;
}
function _temp3(x) {
return x === null;
}
function _temp2(item) {
return item != null;
}
function _temp(node) {
return node.items;
}

```
### Eval output
(kind: exception) Fixture not implemented

0 comments on commit 1b74782

Please sign in to comment.