Update on "[compiler] Special-case phi inference for mixed readonly type"

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-poisoned]
This commit is contained in:
Joe Savona
2024-08-22 17:44:14 -07:00
parent d3ed495294
commit e291fc3f71
@@ -499,22 +499,12 @@ class Unifier {
if (candidateType === null) {
candidateType = resolved;
} else if (!typeEquals(resolved, candidateType)) {
function isArrayOrMixedReadonly(ty: Type): ty is ObjectType {
return (
ty.kind === 'Object' &&
(ty.shapeId === BuiltInArrayId ||
ty.shapeId === BuiltInMixedReadonlyId)
);
}
if (
isArrayOrMixedReadonly(resolved) &&
isArrayOrMixedReadonly(candidateType)
) {
candidateType =
resolved.shapeId === BuiltInArrayId ? resolved : candidateType;
} else {
const unionType = tryUnionTypes(resolved, candidateType);
if (unionType === null) {
candidateType = null;
break;
} else {
candidateType = unionType;
}
} // else same type, continue
}
@@ -668,3 +658,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;
}