Skip to content

Commit 0a8bea2

Browse files
committed
Update on "[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-poisoned]
1 parent 7a6efb2 commit 0a8bea2

File tree

2 files changed

+32
-72
lines changed

2 files changed

+32
-72
lines changed

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

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
Type,
3434
ValidatedIdentifier,
3535
ValueKind,
36+
getHookKindForType,
3637
makeBlockId,
3738
makeIdentifierId,
3839
makeIdentifierName,
@@ -734,11 +735,9 @@ export class Environment {
734735
}
735736
const moduleConfig = parsedModuleConfig.data;
736737
moduleType = installTypeConfig(
737-
moduleName,
738738
this.#globals,
739739
this.#shapes,
740740
moduleConfig,
741-
moduleName,
742741
);
743742
} else {
744743
moduleType = null;
@@ -789,13 +788,23 @@ export class Environment {
789788
(isHookName(binding.imported) ? this.#getCustomHookType() : null)
790789
);
791790
} else {
791+
const expectHook =
792+
isHookName(binding.imported) || isHookName(binding.name);
792793
const moduleType = this.#resolveModuleType(binding.module, loc);
793794
if (moduleType !== null) {
794795
const importedType = this.getPropertyType(
795796
moduleType,
796797
binding.imported,
797798
);
798799
if (importedType != null) {
800+
const isHook = getHookKindForType(this, importedType) != null;
801+
if (expectHook !== isHook) {
802+
CompilerError.throwInvalidConfig({
803+
reason: `Invalid type configuration for module`,
804+
description: `Expected type for '${binding.module}.${binding.imported}' to ${expectHook ? 'be a hook' : 'not be a hook'} based on its name`,
805+
loc,
806+
});
807+
}
799808
return importedType;
800809
}
801810
}
@@ -808,9 +817,7 @@ export class Environment {
808817
* `import {useHook as foo} ...`
809818
* `import {foo as useHook} ...`
810819
*/
811-
return isHookName(binding.imported) || isHookName(binding.name)
812-
? this.#getCustomHookType()
813-
: null;
820+
return expectHook ? this.#getCustomHookType() : null;
814821
}
815822
}
816823
case 'ImportDefault':
@@ -822,18 +829,31 @@ export class Environment {
822829
(isHookName(binding.name) ? this.#getCustomHookType() : null)
823830
);
824831
} else {
832+
const expectHook = isHookName(binding.name);
825833
const moduleType = this.#resolveModuleType(binding.module, loc);
826834
if (moduleType !== null) {
835+
let importedType: Type | null = null;
827836
if (binding.kind === 'ImportDefault') {
828837
const defaultType = this.getPropertyType(moduleType, 'default');
829838
if (defaultType !== null) {
830-
return defaultType;
839+
importedType = defaultType;
831840
}
832841
} else {
833-
return moduleType;
842+
importedType = moduleType;
843+
}
844+
if (importedType !== null) {
845+
const isHook = getHookKindForType(this, importedType) != null;
846+
if (expectHook !== isHook) {
847+
CompilerError.throwInvalidConfig({
848+
reason: `Invalid type configuration for module`,
849+
description: `Expected type for '${binding.module}' (as '${binding.name}') to ${expectHook ? 'be a hook' : 'not be a hook'} based on its name`,
850+
loc,
851+
});
852+
}
853+
return importedType;
834854
}
835855
}
836-
return isHookName(binding.name) ? this.#getCustomHookType() : null;
856+
return expectHook ? this.#getCustomHookType() : null;
837857
}
838858
}
839859
}

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

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

8-
import {Effect, GeneratedSource, ValueKind, ValueReason} from './HIR';
8+
import {Effect, ValueKind, ValueReason} from './HIR';
99
import {
1010
BUILTIN_SHAPES,
1111
BuiltInArrayId,
12-
BuiltInJsxId,
1312
BuiltInMixedReadonlyId,
1413
BuiltInUseActionStateId,
1514
BuiltInUseContextHookId,
@@ -29,8 +28,6 @@ import {
2928
import {BuiltInType, PolyType} from './Types';
3029
import {TypeConfig} from './TypeSchema';
3130
import {assertExhaustive} from '../Utils/utils';
32-
import {isHookName} from './Environment';
33-
import {CompilerError} from '..';
3431

3532
/*
3633
* This file exports types and defaults for JavaScript global objects.
@@ -535,50 +532,6 @@ DEFAULT_GLOBALS.set(
535532
);
536533

537534
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,
582535
globals: GlobalRegistry,
583536
shapes: ShapeRegistry,
584537
typeConfig: TypeConfig,
@@ -601,9 +554,6 @@ function convertTypeConfig(
601554
case 'Any': {
602555
return {kind: 'Poly'};
603556
}
604-
case 'JSX': {
605-
return {kind: 'Object', shapeId: BuiltInJsxId};
606-
}
607557
default: {
608558
assertExhaustive(
609559
typeConfig.name,
@@ -617,12 +567,7 @@ function convertTypeConfig(
617567
positionalParams: typeConfig.positionalParams,
618568
restParam: typeConfig.restParam,
619569
calleeEffect: typeConfig.calleeEffect,
620-
returnType: convertTypeConfig(
621-
moduleName,
622-
globals,
623-
shapes,
624-
typeConfig.returnType,
625-
),
570+
returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
626571
returnValueKind: typeConfig.returnValueKind,
627572
noAlias: typeConfig.noAlias === true,
628573
mutableOnlyIfOperandsAreMutable:
@@ -635,12 +580,7 @@ function convertTypeConfig(
635580
positionalParams: typeConfig.positionalParams ?? [],
636581
restParam: typeConfig.restParam ?? Effect.Freeze,
637582
calleeEffect: Effect.Read,
638-
returnType: convertTypeConfig(
639-
moduleName,
640-
globals,
641-
shapes,
642-
typeConfig.returnType,
643-
),
583+
returnType: installTypeConfig(globals, shapes, typeConfig.returnType),
644584
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
645585
noAlias: typeConfig.noAlias === true,
646586
});
@@ -651,7 +591,7 @@ function convertTypeConfig(
651591
null,
652592
Object.entries(typeConfig.properties ?? {}).map(([key, value]) => [
653593
key,
654-
installTypeConfig(moduleName, globals, shapes, value, key),
594+
installTypeConfig(globals, shapes, value),
655595
]),
656596
);
657597
}

0 commit comments

Comments
 (0)