Skip to content

Commit 1285836

Browse files
committed
[compiler] Type provider infra for tests
ghstack-source-id: 012cb6f Pull Request resolved: #30776
1 parent 8ece668 commit 1285836

29 files changed

+1020
-57
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
NonLocalBinding,
3030
PolyType,
3131
ScopeId,
32+
SourceLocation,
3233
Type,
3334
ValidatedIdentifier,
3435
ValueKind,
@@ -126,11 +127,6 @@ const HookSchema = z.object({
126127

127128
export type Hook = z.infer<typeof HookSchema>;
128129

129-
export const ModuleTypeResolver = z
130-
.function()
131-
.args(z.string())
132-
.returns(z.nullable(TypeSchema));
133-
134130
/*
135131
* TODO(mofeiZ): User defined global types (with corresponding shapes).
136132
* User defined global types should have inline ObjectShapes instead of directly
@@ -148,7 +144,7 @@ const EnvironmentConfigSchema = z.object({
148144
* A function that, given the name of a module, can optionally return a description
149145
* of that module's type signature.
150146
*/
151-
resolveModuleTypeSchema: z.nullable(ModuleTypeResolver).default(null),
147+
moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null),
152148

153149
/**
154150
* A list of functions which the application compiles as macros, where
@@ -712,19 +708,27 @@ export class Environment {
712708
return this.#outlinedFunctions;
713709
}
714710

715-
#resolveModuleType(moduleName: string): Global | null {
716-
if (this.config.resolveModuleTypeSchema == null) {
711+
#resolveModuleType(moduleName: string, loc: SourceLocation): Global | null {
712+
if (this.config.moduleTypeProvider == null) {
717713
return null;
718714
}
719715
let moduleType = this.#moduleTypes.get(moduleName);
720716
if (moduleType === undefined) {
721-
const moduleConfig = this.config.resolveModuleTypeSchema(moduleName);
722-
if (moduleConfig != null) {
723-
const moduleTypes = TypeSchema.parse(moduleConfig);
717+
const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName);
718+
if (unparsedModuleConfig != null) {
719+
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
720+
if (!parsedModuleConfig.success) {
721+
CompilerError.throwInvalidConfig({
722+
reason: `Could not parse module type, the configured \`moduleTypeProvider\` function returned an invalid module description`,
723+
description: parsedModuleConfig.error.toString(),
724+
loc,
725+
});
726+
}
727+
const moduleConfig = parsedModuleConfig.data;
724728
moduleType = installTypeConfig(
725729
this.#globals,
726730
this.#shapes,
727-
moduleTypes,
731+
moduleConfig,
728732
);
729733
} else {
730734
moduleType = null;
@@ -734,7 +738,10 @@ export class Environment {
734738
return moduleType;
735739
}
736740

737-
getGlobalDeclaration(binding: NonLocalBinding): Global | null {
741+
getGlobalDeclaration(
742+
binding: NonLocalBinding,
743+
loc: SourceLocation,
744+
): Global | null {
738745
if (this.config.hookPattern != null) {
739746
const match = new RegExp(this.config.hookPattern).exec(binding.name);
740747
if (
@@ -772,7 +779,7 @@ export class Environment {
772779
(isHookName(binding.imported) ? this.#getCustomHookType() : null)
773780
);
774781
} else {
775-
const moduleType = this.#resolveModuleType(binding.module);
782+
const moduleType = this.#resolveModuleType(binding.module, loc);
776783
if (moduleType !== null) {
777784
const importedType = this.getPropertyType(
778785
moduleType,
@@ -805,10 +812,16 @@ export class Environment {
805812
(isHookName(binding.name) ? this.#getCustomHookType() : null)
806813
);
807814
} else {
808-
const moduleType = this.#resolveModuleType(binding.module);
815+
const moduleType = this.#resolveModuleType(binding.module, loc);
809816
if (moduleType !== null) {
810-
// TODO: distinguish default/namespace cases
811-
return moduleType;
817+
if (binding.kind === 'ImportDefault') {
818+
const defaultType = this.getPropertyType(moduleType, 'default');
819+
if (defaultType !== null) {
820+
return defaultType;
821+
}
822+
} else {
823+
return moduleType;
824+
}
812825
}
813826
return isHookName(binding.name) ? this.#getCustomHookType() : null;
814827
}
@@ -819,9 +832,7 @@ export class Environment {
819832
#isKnownReactModule(moduleName: string): boolean {
820833
return (
821834
moduleName.toLowerCase() === 'react' ||
822-
moduleName.toLowerCase() === 'react-dom' ||
823-
(this.config.enableSharedRuntime__testonly &&
824-
moduleName === 'shared-runtime')
835+
moduleName.toLowerCase() === 'react-dom'
825836
);
826837
}
827838

compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,9 @@ export function installTypeConfig(
551551
case 'Ref': {
552552
return {kind: 'Object', shapeId: BuiltInUseRefId};
553553
}
554+
case 'Any': {
555+
return {kind: 'Poly'};
556+
}
554557
default: {
555558
assertExhaustive(
556559
typeConfig.name,
@@ -566,6 +569,20 @@ export function installTypeConfig(
566569
calleeEffect: typeConfig.calleeEffect,
567570
returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
568571
returnValueKind: typeConfig.returnValueKind,
572+
noAlias: typeConfig.noAlias === true,
573+
mutableOnlyIfOperandsAreMutable:
574+
typeConfig.mutableOnlyIfOperandsAreMutable === true,
575+
});
576+
}
577+
case 'hook': {
578+
return addHook(shapes, {
579+
hookKind: 'Custom',
580+
positionalParams: typeConfig.positionalParams ?? [],
581+
restParam: typeConfig.restParam ?? Effect.Freeze,
582+
calleeEffect: Effect.Read,
583+
returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
584+
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
585+
noAlias: typeConfig.noAlias === true,
569586
});
570587
}
571588
case 'object': {
@@ -578,6 +595,12 @@ export function installTypeConfig(
578595
]),
579596
);
580597
}
598+
default: {
599+
assertExhaustive(
600+
typeConfig,
601+
`Unexpected type kind '${(typeConfig as any).kind}'`,
602+
);
603+
}
581604
}
582605
}
583606

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,15 @@ export enum ValueKind {
13611361
Context = 'context',
13621362
}
13631363

1364+
export const ValueKindSchema = z.enum([
1365+
ValueKind.MaybeFrozen,
1366+
ValueKind.Frozen,
1367+
ValueKind.Primitive,
1368+
ValueKind.Global,
1369+
ValueKind.Mutable,
1370+
ValueKind.Context,
1371+
]);
1372+
13641373
// The effect with which a value is modified.
13651374
export enum Effect {
13661375
// Default value: not allowed after lifetime inference

compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import {isValidIdentifier} from '@babel/types';
99
import {z} from 'zod';
1010
import {Effect, ValueKind} from '..';
11-
import {EffectSchema} from './HIR';
11+
import {EffectSchema, ValueKindSchema} from './HIR';
1212

1313
export type ObjectPropertiesConfig = {[key: string]: TypeConfig};
1414
export const ObjectPropertiesSchema: z.ZodType<ObjectPropertiesConfig> = z
@@ -18,9 +18,9 @@ export const ObjectPropertiesSchema: z.ZodType<ObjectPropertiesConfig> = z
1818
)
1919
.refine(record => {
2020
return Object.keys(record).every(
21-
key => key === '*' || isValidIdentifier(key),
21+
key => key === '*' || key === 'default' || isValidIdentifier(key),
2222
);
23-
}, 'Expected all "object" property names to be valid identifiers or `*` to match any property');
23+
}, 'Expected all "object" property names to be valid identifier, `*` to match any property, of `default` to define a module default export');
2424

2525
export type ObjectTypeConfig = {
2626
kind: 'object';
@@ -38,18 +38,45 @@ export type FunctionTypeConfig = {
3838
calleeEffect: Effect;
3939
returnType: TypeConfig;
4040
returnValueKind: ValueKind;
41+
noAlias?: boolean | null | undefined;
42+
mutableOnlyIfOperandsAreMutable?: boolean | null | undefined;
4143
};
4244
export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
4345
kind: z.literal('function'),
4446
positionalParams: z.array(EffectSchema),
4547
restParam: EffectSchema.nullable(),
4648
calleeEffect: EffectSchema,
4749
returnType: z.lazy(() => TypeSchema),
48-
returnValueKind: z.nativeEnum(ValueKind),
50+
returnValueKind: ValueKindSchema,
51+
noAlias: z.boolean().nullable().optional(),
52+
mutableOnlyIfOperandsAreMutable: z.boolean().nullable().optional(),
4953
});
5054

51-
export type BuiltInTypeConfig = 'Ref' | 'Array' | 'Primitive' | 'MixedReadonly';
55+
export type HookTypeConfig = {
56+
kind: 'hook';
57+
positionalParams?: Array<Effect> | null | undefined;
58+
restParam?: Effect | null | undefined;
59+
returnType: TypeConfig;
60+
returnValueKind?: ValueKind | null | undefined;
61+
noAlias?: boolean | null | undefined;
62+
};
63+
export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
64+
kind: z.literal('hook'),
65+
positionalParams: z.array(EffectSchema).nullable().optional(),
66+
restParam: EffectSchema.nullable().optional(),
67+
returnType: z.lazy(() => TypeSchema),
68+
returnValueKind: ValueKindSchema.nullable().optional(),
69+
noAlias: z.boolean().nullable().optional(),
70+
});
71+
72+
export type BuiltInTypeConfig =
73+
| 'Any'
74+
| 'Ref'
75+
| 'Array'
76+
| 'Primitive'
77+
| 'MixedReadonly';
5278
export const BuiltInTypeSchema: z.ZodType<BuiltInTypeConfig> = z.union([
79+
z.literal('Any'),
5380
z.literal('Ref'),
5481
z.literal('Array'),
5582
z.literal('Primitive'),
@@ -68,9 +95,11 @@ export const TypeReferenceSchema: z.ZodType<TypeReferenceConfig> = z.object({
6895
export type TypeConfig =
6996
| ObjectTypeConfig
7097
| FunctionTypeConfig
98+
| HookTypeConfig
7199
| TypeReferenceConfig;
72100
export const TypeSchema: z.ZodType<TypeConfig> = z.union([
73101
ObjectTypeSchema,
74102
FunctionTypeSchema,
103+
HookTypeSchema,
75104
TypeReferenceSchema,
76105
]);

compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function collectTemporaries(
127127
break;
128128
}
129129
case 'LoadGlobal': {
130-
const global = env.getGlobalDeclaration(value.binding);
130+
const global = env.getGlobalDeclaration(value.binding, value.loc);
131131
const hookKind = global !== null ? getHookKindForType(env, global) : null;
132132
const lvalId = instr.lvalue.identifier.id;
133133
if (hookKind === 'useMemo' || hookKind === 'useCallback') {

compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ function* generateInstructionTypes(
227227
}
228228

229229
case 'LoadGlobal': {
230-
const globalType = env.getGlobalDeclaration(value.binding);
230+
const globalType = env.getGlobalDeclaration(value.binding, value.loc);
231231
if (globalType) {
232232
yield equation(left, globalType);
233233
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
## Input
33

44
```javascript
5+
import { useFragment } from "shared-runtime";
6+
57
function Component(props) {
68
const post = useFragment(
79
graphql`
@@ -36,6 +38,8 @@ function Component(props) {
3638

3739
```javascript
3840
import { c as _c } from "react/compiler-runtime";
41+
import { useFragment } from "shared-runtime";
42+
3943
function Component(props) {
4044
const $ = _c(4);
4145
const post = useFragment(

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useFragment } from "shared-runtime";
2+
13
function Component(props) {
24
const post = useFragment(
35
graphql`

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-logical.expect.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
## Input
33

44
```javascript
5+
import { useFragment } from "shared-runtime";
6+
57
function Component(props) {
68
const item = useFragment(
79
graphql`
@@ -20,6 +22,8 @@ function Component(props) {
2022
2123
```javascript
2224
import { c as _c } from "react/compiler-runtime";
25+
import { useFragment } from "shared-runtime";
26+
2327
function Component(props) {
2428
const $ = _c(2);
2529
const item = useFragment(

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-logical.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useFragment } from "shared-runtime";
2+
13
function Component(props) {
24
const item = useFragment(
35
graphql`

0 commit comments

Comments
 (0)