Skip to content

Commit e458964

Browse files
committed
[compiler] Validate type configs for hooks/non-hooks
Alternative to #30868. The goal is to ensure that the types coming out of moduleTypeProvider are valid wrt to hook typing. If something is named like a hook, then it must be typed as a hook (or don't type it). ghstack-source-id: 787575b Pull Request resolved: #30888
1 parent 4c58fce commit e458964

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,9 +734,11 @@ export class Environment {
734734
}
735735
const moduleConfig = parsedModuleConfig.data;
736736
moduleType = installTypeConfig(
737+
moduleName,
737738
this.#globals,
738739
this.#shapes,
739740
moduleConfig,
741+
moduleName,
740742
);
741743
} else {
742744
moduleType = null;

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

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {Effect, ValueKind, ValueReason} from './HIR';
8+
import {Effect, GeneratedSource, ValueKind, ValueReason} from './HIR';
99
import {
1010
BUILTIN_SHAPES,
1111
BuiltInArrayId,
12+
BuiltInJsxId,
1213
BuiltInMixedReadonlyId,
1314
BuiltInUseActionStateId,
1415
BuiltInUseContextHookId,
@@ -28,6 +29,8 @@ import {
2829
import {BuiltInType, PolyType} from './Types';
2930
import {TypeConfig} from './TypeSchema';
3031
import {assertExhaustive} from '../Utils/utils';
32+
import {isHookName} from './Environment';
33+
import {CompilerError} from '..';
3134

3235
/*
3336
* This file exports types and defaults for JavaScript global objects.
@@ -532,6 +535,50 @@ DEFAULT_GLOBALS.set(
532535
);
533536

534537
export function installTypeConfig(
538+
moduleName: string,
539+
globals: GlobalRegistry,
540+
shapes: ShapeRegistry,
541+
typeConfig: TypeConfig,
542+
moduleOrPropertyName: string | null,
543+
): Global {
544+
const type = convertTypeConfig(moduleName, globals, shapes, typeConfig);
545+
if (moduleOrPropertyName !== null) {
546+
if (isHookName(moduleOrPropertyName)) {
547+
// Named like a hook => must be typed as a hook
548+
if (type.kind !== 'Function' || type.shapeId == null) {
549+
CompilerError.throwInvalidConfig({
550+
reason: `Invalid moduleTypeProvider result for module '${moduleName}', expected type for '${moduleOrPropertyName}' to be a hook based on its name, but the type was not a hook`,
551+
loc: GeneratedSource,
552+
});
553+
}
554+
const functionType = shapes.get(type.shapeId);
555+
if (functionType == null || functionType.functionType?.hookKind == null) {
556+
CompilerError.throwInvalidConfig({
557+
reason: `Invalid moduleTypeProvider result for module '${moduleName}', expected type for '${moduleOrPropertyName}' to be a hook based on its name, but the type was not a hook`,
558+
loc: GeneratedSource,
559+
});
560+
}
561+
} else {
562+
// Not named like a hook => must not be a hook
563+
if (type.kind === 'Function' && type.shapeId != null) {
564+
const functionType = shapes.get(type.shapeId);
565+
if (
566+
functionType != null &&
567+
functionType.functionType?.hookKind != null
568+
) {
569+
CompilerError.throwInvalidConfig({
570+
reason: `Invalid moduleTypeProvider result for module '${moduleName}', expected type for '${moduleOrPropertyName}' not to be a hook, but it was typed as a hook`,
571+
loc: GeneratedSource,
572+
});
573+
}
574+
}
575+
}
576+
}
577+
return type;
578+
}
579+
580+
function convertTypeConfig(
581+
moduleName: string,
535582
globals: GlobalRegistry,
536583
shapes: ShapeRegistry,
537584
typeConfig: TypeConfig,
@@ -554,6 +601,9 @@ export function installTypeConfig(
554601
case 'Any': {
555602
return {kind: 'Poly'};
556603
}
604+
case 'JSX': {
605+
return {kind: 'Object', shapeId: BuiltInJsxId};
606+
}
557607
default: {
558608
assertExhaustive(
559609
typeConfig.name,
@@ -567,7 +617,12 @@ export function installTypeConfig(
567617
positionalParams: typeConfig.positionalParams,
568618
restParam: typeConfig.restParam,
569619
calleeEffect: typeConfig.calleeEffect,
570-
returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
620+
returnType: convertTypeConfig(
621+
moduleName,
622+
globals,
623+
shapes,
624+
typeConfig.returnType,
625+
),
571626
returnValueKind: typeConfig.returnValueKind,
572627
noAlias: typeConfig.noAlias === true,
573628
mutableOnlyIfOperandsAreMutable:
@@ -580,7 +635,12 @@ export function installTypeConfig(
580635
positionalParams: typeConfig.positionalParams ?? [],
581636
restParam: typeConfig.restParam ?? Effect.Freeze,
582637
calleeEffect: Effect.Read,
583-
returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
638+
returnType: convertTypeConfig(
639+
moduleName,
640+
globals,
641+
shapes,
642+
typeConfig.returnType,
643+
),
584644
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
585645
noAlias: typeConfig.noAlias === true,
586646
});
@@ -591,7 +651,7 @@ export function installTypeConfig(
591651
null,
592652
Object.entries(typeConfig.properties ?? {}).map(([key, value]) => [
593653
key,
594-
installTypeConfig(globals, shapes, value),
654+
installTypeConfig(moduleName, globals, shapes, value, key),
595655
]),
596656
);
597657
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,15 @@ export type BuiltInTypeConfig =
7474
| 'Ref'
7575
| 'Array'
7676
| 'Primitive'
77-
| 'MixedReadonly';
77+
| 'MixedReadonly'
78+
| 'JSX';
7879
export const BuiltInTypeSchema: z.ZodType<BuiltInTypeConfig> = z.union([
7980
z.literal('Any'),
8081
z.literal('Ref'),
8182
z.literal('Array'),
8283
z.literal('Primitive'),
8384
z.literal('MixedReadonly'),
85+
z.literal('JSX'),
8486
]);
8587

8688
export type TypeReferenceConfig = {

0 commit comments

Comments
 (0)