From bc8ab8ca6d511f968133a085995b2b4fbe21cccc Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Tue, 19 Nov 2024 20:45:40 -0500 Subject: [PATCH] [compiler][nocommit] Quick sketch of types on places MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a quick sketch of moving types from Identifier to Places so that we can have flow-sensitive types. The intent isn't to ship this but to quickly explore in order to figure out concrete challenges, to inform a "real" implementation. Some observations: * ReactiveScopeDependency/Declaration now need types. We use the type of their identifier currently, so we'd have to populate a type for them instead. But if we do flow-sensitive types, there won't be one obvious correct type to use! Consider a scope that uses `x` twice, once where we can infer its a primitive and one where we can't. We should treat this like phi typing and only infer a precise type for the dep/decl if all references have the same type. * InferMutableRanges's aliasing logic uses a `DisjointSet` and checks the types for some things (refs in particular). So the obvious approach is to replace that with a `DisjointSet`. While doing that I was reminded that the way we handle aliasing for phis is kind of weird. We currently delay creating an alias until we know the phi is mutated later, but we don't do the same thing for things like `x = y` (ie we eagerly alias). Switching to paths is a good chance to revisit the aliasing. * InferTypes gets tricky because we still want different places with the same identifier to get the same type (for now, until we introduce flow-sensitive typing). But every Place has its own type instance. So for now we can basically keep a mapping of IdentifierId to a canonical Type and use this for all the inference. The actual implementation in the PR is messier than that since i started with a variant of flow-sensitive typing and then rolled it back. For actual flow-sensitive typing (not implemented here) there's a sort of inverse phi situation. Consider a variant of mofeiZ's recent find: ``` function Component({y}) { let x = makeValue(y); let result; if (...cond...) { result = ...x... // do something with x } else { result = ...x... // do something else with x } return result; } ``` If both branches of the if can infer `x` as a number, then it's sound to infer `makeValue(y)` as producing a number. However, if you take away the else branch then it might not be, since now there's a code path (the fallthrough) in which we're not sure of the type. This is just like a phi for variable reassignment, but at the type level. And it's also happening in reverse — the later "operands" (usages of x) flow backwards into the "phi" that is the type of x before the if/else. We'd have to build up a mapping like this and build the appropriate type equations. ghstack-source-id: 8360182e329a33ed345bbba1db55e94be32d8461 Pull Request resolved: https://github.com/facebook/react/pull/31575 --- .../src/HIR/BuildHIR.ts | 10 ++ .../src/HIR/CollectHoistablePropertyLoads.ts | 4 + .../HIR/CollectOptionalChainDependencies.ts | 3 + .../src/HIR/DeriveMinimalDependenciesHIR.ts | 3 +- .../src/HIR/HIR.ts | 149 ++++++++---------- .../src/HIR/HIRBuilder.ts | 4 +- .../src/HIR/MergeConsecutiveBlocks.ts | 1 + .../src/HIR/PrintHIR.ts | 4 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 19 ++- .../src/Inference/AnalyseFunctions.ts | 2 +- .../src/Inference/DropManualMemoization.ts | 3 + .../src/Inference/InferAlias.ts | 104 ++++++++++-- .../src/Inference/InferAliasForPhis.ts | 6 +- .../src/Inference/InferAliasForStores.ts | 14 +- .../src/Inference/InferFunctionEffects.ts | 6 +- .../src/Inference/InferMutableLifetimes.ts | 4 +- .../src/Inference/InferMutableRanges.ts | 14 +- .../Inference/InferMutableRangesForAlias.ts | 28 ++-- .../src/Inference/InferReactivePlaces.ts | 12 +- .../src/Inference/InferReferenceEffects.ts | 16 +- .../src/Inference/InferTryCatchAliases.ts | 13 +- .../src/Optimization/InlineJsxTransform.ts | 1 + .../src/Optimization/LowerContextAccess.ts | 4 +- .../ReactiveScopes/CodegenReactiveFunction.ts | 6 +- .../CollectReactiveIdentifiers.ts | 8 +- .../DeriveMinimalDependencies.ts | 10 +- .../FlattenScopesWithHooksOrUseHIR.ts | 6 +- .../InferReactiveScopeVariables.ts | 2 +- ...rgeReactiveScopesThatInvalidateTogether.ts | 5 +- .../ReactiveScopes/PrintReactiveFunction.ts | 3 +- .../ReactiveScopes/PropagateEarlyReturns.ts | 4 + .../PruneInitializationDependencies.ts | 18 ++- .../ReactiveScopes/PruneNonEscapingScopes.ts | 25 +-- .../PruneNonReactiveDependencies.ts | 4 +- .../src/SSA/EnterSSA.ts | 2 - .../src/TypeInference/InferTypes.ts | 87 +++++----- .../src/Validation/ValidateHooksUsage.ts | 6 +- .../ValidateLocalsNotReassignedAfterRender.ts | 9 +- .../ValidateMemoizedEffectDependencies.ts | 14 +- .../Validation/ValidateNoRefAccesInRender.ts | 10 +- .../ValidateNoSetStateInPassiveEffects.ts | 6 +- .../Validation/ValidateNoSetStateInRender.ts | 4 +- .../ValidatePreservedManualMemoization.ts | 1 + .../fixtures/compiler/type-inference.js | 12 ++ 44 files changed, 383 insertions(+), 283 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index ecc22365dd..519c590611 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -83,6 +83,7 @@ export function lower( identifier: builder.resolveBinding(ref), effect: Effect.Unknown, reactive: false, + type: makeType(), loc: ref.loc ?? GeneratedSource, }); } @@ -114,6 +115,7 @@ export function lower( identifier: binding.identifier, effect: Effect.Unknown, reactive: false, + type: makeType(), loc: param.node.loc ?? GeneratedSource, }; params.push(place); @@ -127,6 +129,7 @@ export function lower( identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource), effect: Effect.Unknown, reactive: false, + type: makeType(), loc: param.node.loc ?? GeneratedSource, }; promoteTemporary(place.identifier); @@ -145,6 +148,7 @@ export function lower( identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource), effect: Effect.Unknown, reactive: false, + type: makeType(), loc: param.node.loc ?? GeneratedSource, }; params.push({ @@ -463,6 +467,7 @@ function lowerStatement( identifier: identifier.identifier, kind: 'Identifier', reactive: false, + type: makeType(), loc: id.node.loc ?? GeneratedSource, }; lowerValueToTemporary(builder, { @@ -856,6 +861,7 @@ function lowerStatement( identifier: binding.identifier, kind: 'Identifier', reactive: false, + type: makeType(), loc: id.node.loc ?? GeneratedSource, }; if (builder.isContextIdentifier(id)) { @@ -1265,6 +1271,7 @@ function lowerStatement( ), effect: Effect.Unknown, reactive: false, + type: makeType(), loc: handlerBindingPath.node.loc ?? GeneratedSource, }; promoteTemporary(place.identifier); @@ -3435,6 +3442,7 @@ function lowerIdentifier( identifier: binding.identifier, effect: Effect.Unknown, reactive: false, + type: makeType(), loc: exprLoc, }; return place; @@ -3456,6 +3464,7 @@ function buildTemporaryPlace(builder: HIRBuilder, loc: SourceLocation): Place { identifier: builder.makeTemporary(loc), effect: Effect.Unknown, reactive: false, + type: makeType(), loc, }; return place; @@ -3518,6 +3527,7 @@ function lowerIdentifierForAssignment( identifier: binding.identifier, effect: Effect.Unknown, reactive: false, + type: makeType(), loc, }; return place; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8..4180691417 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -18,6 +18,7 @@ import { IdentifierId, InstructionId, InstructionValue, + makeType, ReactiveScopeDependency, ScopeId, } from './HIR'; @@ -216,6 +217,7 @@ class PropertyPathRegistry { optionalProperties: new Map(), fullPath: { identifier, + type: makeType(), path: [], }, hasOptional: false, @@ -239,6 +241,7 @@ class PropertyPathRegistry { parent: parent, fullPath: { identifier: parent.fullPath.identifier, + type: makeType(), path: parent.fullPath.path.concat(entry), }, hasOptional: parent.hasOptional || entry.optional, @@ -280,6 +283,7 @@ function getMaybeNonNullInInstruction( if (instr.kind === 'PropertyLoad') { path = context.temporaries.get(instr.object.identifier.id) ?? { identifier: instr.object.identifier, + type: makeType(), path: [], }; } else if (instr.kind === 'Destructure') { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 2b7c9f2134..565353f922 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -16,6 +16,7 @@ import { DependencyPathEntry, Instruction, Terminal, + makeType, } from './HIR'; import {printIdentifier} from './PrintHIR'; @@ -282,6 +283,7 @@ function traverseOptionalBlock( ); baseObject = { identifier: maybeTest.instructions[0].value.place.identifier, + type: maybeTest.instructions[0].value.place.type, path, }; test = maybeTest.terminal; @@ -383,6 +385,7 @@ function traverseOptionalBlock( ); const load = { identifier: baseObject.identifier, + type: makeType(), path: [ ...baseObject.path, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts index f5567b3e53..bbfd11c503 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts @@ -10,6 +10,7 @@ import { DependencyPathEntry, GeneratedSource, Identifier, + makeType, ReactiveScopeDependency, } from '../HIR'; import {printIdentifier} from '../HIR/PrintHIR'; @@ -308,7 +309,7 @@ function collectMinimalDependenciesInSubtree( results: Set, ): void { if (isDependency(node.accessType)) { - results.add({identifier: rootIdentifier, path}); + results.add({identifier: rootIdentifier, type: makeType(), path}); } else { for (const [childName, childNode] of node.properties) { collectMinimalDependenciesInSubtree( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 954fb6f400..d7f6422966 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -11,7 +11,7 @@ import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {Environment, ReactFunctionType} from './Environment'; import {HookKind} from './ObjectShape'; -import {Type, makeType} from './Types'; +import {Type} from './Types'; import {z} from 'zod'; /* @@ -1108,6 +1108,7 @@ export type Place = { identifier: Identifier; effect: Effect; reactive: boolean; + type: Type; loc: SourceLocation; }; @@ -1211,7 +1212,6 @@ export type Identifier = { * variables may have the same scope id. */ scope: ReactiveScope | null; - type: Type; loc: SourceLocation; }; @@ -1238,7 +1238,6 @@ export function makeTemporaryIdentifier( declarationId: makeDeclarationId(id), mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)}, scope: null, - type: makeType(), loc, }; } @@ -1508,6 +1507,7 @@ export type ReactiveScopeDependencies = Set; export type ReactiveScopeDeclaration = { identifier: Identifier; + type: Type; scope: ReactiveScope; // the scope in which the variable was originally declared }; @@ -1515,6 +1515,7 @@ export type DependencyPathEntry = {property: string; optional: boolean}; export type DependencyPath = Array; export type ReactiveScopeDependency = { identifier: Identifier; + type: Type; path: DependencyPath; }; @@ -1628,110 +1629,92 @@ export function makeInstructionId(id: number): InstructionId { return id as InstructionId; } -export function isObjectMethodType(id: Identifier): boolean { - return id.type.kind == 'ObjectMethod'; +export function isObjectMethodType(type: Type): boolean { + return type.kind == 'ObjectMethod'; } -export function isObjectType(id: Identifier): boolean { - return id.type.kind === 'Object'; +export function isObjectType(type: Type): boolean { + return type.kind === 'Object'; } -export function isPrimitiveType(id: Identifier): boolean { - return id.type.kind === 'Primitive'; +export function isPrimitiveType(type: Type): boolean { + return type.kind === 'Primitive'; } -export function isArrayType(id: Identifier): boolean { - return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray'; +export function isArrayType(type: Type): boolean { + return type.kind === 'Object' && type.shapeId === 'BuiltInArray'; } -export function isRefValueType(id: Identifier): boolean { - return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue'; +export function isRefValueType(type: Type): boolean { + return type.kind === 'Object' && type.shapeId === 'BuiltInRefValue'; } -export function isUseRefType(id: Identifier): boolean { - return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseRefId'; +export function isUseRefType(type: Type): boolean { + return type.kind === 'Object' && type.shapeId === 'BuiltInUseRefId'; } -export function isUseStateType(id: Identifier): boolean { - return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState'; +export function isUseStateType(type: Type): boolean { + return type.kind === 'Object' && type.shapeId === 'BuiltInUseState'; } -export function isRefOrRefValue(id: Identifier): boolean { - return isUseRefType(id) || isRefValueType(id); +export function isRefOrRefValue(type: Type): boolean { + return isUseRefType(type) || isRefValueType(type); } -export function isSetStateType(id: Identifier): boolean { - return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState'; +export function isSetStateType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInSetState'; } -export function isUseActionStateType(id: Identifier): boolean { +export function isUseActionStateType(type: Type): boolean { + return type.kind === 'Object' && type.shapeId === 'BuiltInUseActionState'; +} + +export function isStartTransitionType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInStartTransition'; +} + +export function isSetActionStateType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInSetActionState'; +} + +export function isUseReducerType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInUseReducer'; +} + +export function isDispatcherType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInDispatch'; +} + +export function isStableType(type: Type): boolean { return ( - id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseActionState' + isSetStateType(type) || + isSetActionStateType(type) || + isDispatcherType(type) || + isUseRefType(type) || + isStartTransitionType(type) ); } -export function isStartTransitionType(id: Identifier): boolean { +export function isUseEffectHookType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInUseEffectHook'; +} +export function isUseLayoutEffectHookType(type: Type): boolean { return ( - id.type.kind === 'Function' && id.type.shapeId === 'BuiltInStartTransition' + type.kind === 'Function' && type.shapeId === 'BuiltInUseLayoutEffectHook' + ); +} +export function isUseInsertionEffectHookType(type: Type): boolean { + return ( + type.kind === 'Function' && type.shapeId === 'BuiltInUseInsertionEffectHook' ); } -export function isSetActionStateType(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState' - ); +export function isUseContextHookType(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInUseContextHook'; } -export function isUseReducerType(id: Identifier): boolean { - return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseReducer'; -} - -export function isDispatcherType(id: Identifier): boolean { - return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInDispatch'; -} - -export function isStableType(id: Identifier): boolean { - return ( - isSetStateType(id) || - isSetActionStateType(id) || - isDispatcherType(id) || - isUseRefType(id) || - isStartTransitionType(id) - ); -} - -export function isUseEffectHookType(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectHook' - ); -} -export function isUseLayoutEffectHookType(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && - id.type.shapeId === 'BuiltInUseLayoutEffectHook' - ); -} -export function isUseInsertionEffectHookType(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && - id.type.shapeId === 'BuiltInUseInsertionEffectHook' - ); -} - -export function isUseContextHookType(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseContextHook' - ); -} - -export function getHookKind(env: Environment, id: Identifier): HookKind | null { - return getHookKindForType(env, id.type); -} - -export function isUseOperator(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseOperator' - ); +export function isUseOperator(type: Type): boolean { + return type.kind === 'Function' && type.shapeId === 'BuiltInUseOperator'; } export function getHookKindForType( @@ -1745,4 +1728,12 @@ export function getHookKindForType( return null; } +export function isEffectHook(type: Type): boolean { + return ( + isUseEffectHookType(type) || + isUseLayoutEffectHookType(type) || + isUseInsertionEffectHookType(type) + ); +} + export * from './Types'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 9202f2145f..0bbbd529f4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -328,7 +328,6 @@ export default class HIRBuilder { end: makeInstructionId(0), }, scope: null, - type: makeType(), loc: node.loc ?? GeneratedSource, }; this.#bindings.set(name, {node, identifier}); @@ -896,6 +895,7 @@ export function createTemporaryPlace( identifier: makeTemporaryIdentifier(env.nextIdentifierId, loc), reactive: false, effect: Effect.Unknown, + type: makeType(), loc: GeneratedSource, }; } @@ -908,7 +908,7 @@ export function createTemporaryPlace( export function clonePlaceToTemporary(env: Environment, place: Place): Place { const temp = createTemporaryPlace(env, place.loc); temp.effect = place.effect; - temp.identifier.type = place.identifier.type; + temp.type = place.type; temp.reactive = place.reactive; return temp; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts index ea132b772a..8c5bb1e7e2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts @@ -87,6 +87,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void { identifier: phi.place.identifier, effect: Effect.ConditionallyMutate, reactive: false, + type: phi.place.type, loc: GeneratedSource, }, value: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..aec8ee288d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -165,7 +165,7 @@ export function printPhi(phi: Phi): string { const items = []; items.push(printPlace(phi.place)); items.push(printMutableRange(phi.place.identifier)); - items.push(printType(phi.place.identifier.type)); + items.push(printType(phi.place.type)); items.push(': phi('); const phis = []; for (const [blockId, place] of phi.operands) { @@ -837,7 +837,7 @@ export function printPlace(place: Place): string { ' ', printIdentifier(place.identifier), printMutableRange(place.identifier), - printType(place.identifier.type), + printType(place.type), place.reactive ? '{reactive}' : null, ]; return items.filter(x => x != null).join(''); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 8aed17f8ee..371f8a33e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -17,6 +17,7 @@ import { areEqualPaths, IdentifierId, Terminal, + makeType, } from './HIR'; import { collectHoistablePropertyLoads, @@ -278,6 +279,7 @@ function collectTemporariesSidemapImpl( ) { temporaries.set(lvalue.identifier.id, { identifier: value.place.identifier, + type: value.place.type, path: [], }); } @@ -331,11 +333,13 @@ function getProperty( if (resolvedDependency == null) { property = { identifier: object.identifier, + type: makeType(), path: [{property: propertyName, optional}], }; } else { property = { identifier: resolvedDependency.identifier, + type: makeType(), path: [...resolvedDependency.path, {property: propertyName, optional}], }; } @@ -441,7 +445,7 @@ class Context { // Checks if identifier is a valid dependency in the current scope #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { + if (isRefValueType(maybeDependency.type)) { return false; } @@ -449,7 +453,7 @@ class Context { * object methods are not deps because they will be codegen'ed back in to * the object literal. */ - if (isObjectMethodType(maybeDependency.identifier)) { + if (isObjectMethodType(maybeDependency.type)) { return false; } @@ -488,6 +492,7 @@ class Context { this.visitDependency( this.#temporaries.get(place.identifier.id) ?? { identifier: place.identifier, + type: place.type, path: [], }, ); @@ -535,6 +540,7 @@ class Context { ) { scope.declarations.set(maybeDependency.identifier.id, { identifier: maybeDependency.identifier, + type: maybeDependency.type, scope: originalDeclaration.scope.value!, }); } @@ -543,11 +549,12 @@ class Context { // ref.current access is not a valid dep if ( - isUseRefType(maybeDependency.identifier) && + isUseRefType(maybeDependency.type) && maybeDependency.path.at(0)?.property === 'current' ) { maybeDependency = { identifier: maybeDependency.identifier, + type: maybeDependency.type, path: [], }; } @@ -569,7 +576,11 @@ class Context { identifier => identifier.declarationId === place.identifier.declarationId, ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) + this.#checkValidDependency({ + identifier: place.identifier, + type: place.type, + path: [], + }) ) { currentScope.reassignments.add(place.identifier); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..e4cb91077e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -140,7 +140,7 @@ function infer( name = dep.identifier.name; } - if (isRefOrRefValue(dep.identifier)) { + if (isRefOrRefValue(dep.type)) { /* * TODO: this is a hack to ensure we treat functions which reference refs * as having a capture and therefore being considered mutable. this ensures diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 4dcdc21e15..208381d7f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -28,6 +28,7 @@ import { TInstruction, getHookKindForType, makeInstructionId, + makeType, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; @@ -269,6 +270,7 @@ function getManualMemoizationReplacement( identifier: fn.identifier, effect: Effect.Unknown, reactive: false, + type: makeType(), loc, }, loc, @@ -421,6 +423,7 @@ export function dropManualMemoization(func: HIRFunction): void { identifier: fnPlace.identifier, effect: Effect.Unknown, reactive: false, + type: makeType(), loc: fnPlace.loc, }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAlias.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAlias.ts index 80422c8391..e939686b03 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAlias.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAlias.ts @@ -5,64 +5,136 @@ * LICENSE file in the root directory of this source tree. */ +import invariant from 'invariant'; +import {CompilerError} from '..'; import { HIRFunction, Identifier, + IdentifierId, Instruction, isPrimitiveType, Place, } from '../HIR/HIR'; +import {printPlace} from '../HIR/PrintHIR'; +import { + eachInstructionLValue, + eachInstructionValueOperand, + eachPatternOperand, + eachTerminalOperand, +} from '../HIR/visitors'; import DisjointSet from '../Utils/DisjointSet'; export type AliasSet = Set; -export function inferAliases(func: HIRFunction): DisjointSet { - const aliases = new DisjointSet(); +export function inferAliases(func: HIRFunction): DisjointSet { + const aliases = new DisjointSet(); + const declarations = new Map(); + for (const param of func.params) { + const place = param.kind === 'Identifier' ? param : param.place; + declarations.set(place.identifier.id, place); + } for (const [_, block] of func.body.blocks) { + for (const phi of block.phis) { + declarations.set(phi.place.identifier.id, phi.place); + } for (const instr of block.instructions) { - inferInstr(instr, aliases); + inferInstr(instr, aliases, declarations); + } + for (const operand of eachTerminalOperand(block.terminal)) { + const declaration = declarations.get(operand.identifier.id); + if (declaration !== undefined) { + aliases.union([declaration, operand]); + } } } return aliases; } +function assertGet(map: Map, place: Place): Place { + const value = map.get(place.identifier.id); + if (value === undefined) { + CompilerError.invariant(value !== undefined, { + reason: `Missing declaration for ${printPlace(place)}`, + loc: place.loc, + }); + } + return value; +} + function inferInstr( instr: Instruction, - aliases: DisjointSet, + aliases: DisjointSet, + declarations: Map, ): void { const {lvalue, value: instrValue} = instr; - let alias: Place | null = null; switch (instrValue.kind) { case 'LoadLocal': case 'LoadContext': { - if (isPrimitiveType(instrValue.place.identifier)) { - return; + if (!isPrimitiveType(instrValue.place.type)) { + const places = [ + lvalue, + instrValue.place, + assertGet(declarations, instrValue.place), + ]; + aliases.union(places); } - alias = instrValue.place; break; } case 'StoreLocal': case 'StoreContext': { - alias = instrValue.value; + if (!isPrimitiveType(instrValue.value.type)) { + const places = [ + lvalue, + instrValue.lvalue.place, + instrValue.value, + assertGet(declarations, instrValue.value)!, + ]; + aliases.union(places); + } break; } case 'Destructure': { - alias = instrValue.value; + aliases.union([ + lvalue, + ...eachPatternOperand(instrValue.lvalue.pattern), + instrValue.value, + assertGet(declarations, instrValue.value)!, + ]); break; } case 'ComputedLoad': case 'PropertyLoad': { - alias = instrValue.object; + aliases.union([ + lvalue, + instrValue.object, + assertGet(declarations, instrValue.object)!, + ]); break; } case 'TypeCastExpression': { - alias = instrValue.value; + if (!isPrimitiveType(instrValue.value.type)) { + aliases.union([ + lvalue, + instrValue.value, + assertGet(declarations, instrValue.value), + ]); + } + break; + } + default: { + for (const operand of eachInstructionValueOperand(instrValue)) { + const declaration = declarations.get(operand.identifier.id); + if (declaration != null) { + aliases.union([operand, declaration]); + } + } break; } - default: - return; } - - aliases.union([lvalue.identifier, alias.identifier]); + for (const lvalue of eachInstructionLValue(instr)) { + if (!declarations.has(lvalue.identifier.id)) { + declarations.set(lvalue.identifier.id, lvalue); + } + } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts index e81e3ebdae..a77a7e9819 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts @@ -5,12 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import {HIRFunction, Identifier} from '../HIR/HIR'; +import {HIRFunction, Place} from '../HIR/HIR'; import DisjointSet from '../Utils/DisjointSet'; export function inferAliasForPhis( func: HIRFunction, - aliases: DisjointSet, + aliases: DisjointSet, ): void { for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { @@ -19,7 +19,7 @@ export function inferAliasForPhis( (block.instructions.at(0)?.id ?? block.terminal.id); if (isPhiMutatedAfterCreation) { for (const [, operand] of phi.operands) { - aliases.union([phi.place.identifier, operand.identifier]); + aliases.union([phi.place, operand]); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForStores.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForStores.ts index d8d17f6a5f..bd61d91995 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForStores.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForStores.ts @@ -5,13 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - Effect, - HIRFunction, - Identifier, - InstructionId, - Place, -} from '../HIR/HIR'; +import {Effect, HIRFunction, InstructionId, Place} from '../HIR/HIR'; import { eachInstructionLValue, eachInstructionValueOperand, @@ -20,7 +14,7 @@ import DisjointSet from '../Utils/DisjointSet'; export function inferAliasForStores( func: HIRFunction, - aliases: DisjointSet, + aliases: DisjointSet, ): void { for (const [_, block] of func.body.blocks) { for (const instr of block.instructions) { @@ -54,7 +48,7 @@ export function inferAliasForStores( } function maybeAlias( - aliases: DisjointSet, + aliases: DisjointSet, lvalue: Place, rvalue: Place, id: InstructionId, @@ -63,6 +57,6 @@ function maybeAlias( lvalue.identifier.mutableRange.end > id + 1 || rvalue.identifier.mutableRange.end > id ) { - aliases.union([lvalue.identifier, rvalue.identifier]); + aliases.union([lvalue, rvalue]); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index 0ae54839b6..c25117dcea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -16,7 +16,7 @@ import { InstructionValue, Place, ValueReason, - getHookKind, + getHookKindForType, isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; @@ -38,7 +38,7 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { switch (place.effect) { case Effect.Store: case Effect.Mutate: { - if (isRefOrRefValue(place.identifier)) { + if (isRefOrRefValue(place.type)) { break; } else if (value.kind === ValueKind.Context) { return { @@ -235,7 +235,7 @@ export function inferInstructionFunctionEffects( callee = instr.value.callee; } functionEffects.push(...operandEffects(state, callee, false)); - let isHook = getHookKind(env, callee.identifier) != null; + let isHook = getHookKindForType(env, callee.type) != null; for (const arg of instr.value.args) { const place = arg.kind === 'Identifier' ? arg : arg.place; /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts index 508a970d93..dcdf7e6e95 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts @@ -67,7 +67,7 @@ import {assertExhaustive} from '../Utils/utils'; */ function infer(place: Place, instrId: InstructionId): void { - if (!isRefOrRefValue(place.identifier)) { + if (!isRefOrRefValue(place.type)) { place.identifier.mutableRange.end = makeInstructionId(instrId + 1); } } @@ -180,7 +180,7 @@ export function inferMutableLifetimes( ); if ( declaration != null && - !isRefOrRefValue(instr.value.lvalue.place.identifier) + !isRefOrRefValue(instr.value.lvalue.place.type) ) { const range = instr.value.lvalue.place.identifier.mutableRange; if (range.start === 0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts index a8f33b31d5..1ef137f5b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts @@ -5,13 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import {HIRFunction, Identifier} from '../HIR/HIR'; +import prettyFormat from 'pretty-format'; +import {HIRFunction, Place} from '../HIR/HIR'; import {inferAliases} from './InferAlias'; import {inferAliasForPhis} from './InferAliasForPhis'; import {inferAliasForStores} from './InferAliasForStores'; import {inferMutableLifetimes} from './InferMutableLifetimes'; import {inferMutableRangesForAlias} from './InferMutableRangesForAlias'; import {inferTryCatchAliases} from './InferTryCatchAliases'; +import {printIdentifier, printPlace} from '../HIR/PrintHIR'; +import {getOrInsertDefault} from '../Utils/utils'; export function inferMutableRanges(ir: HIRFunction): void { // Infer mutable ranges for non fields @@ -19,6 +22,13 @@ export function inferMutableRanges(ir: HIRFunction): void { // Calculate aliases const aliases = inferAliases(ir); + // const a = aliases.canonicalize(); + // const f = new Map(); + // for (const [k, v] of a) { + // getOrInsertDefault(f, printPlace(v), []).push(printPlace(k)); + // } + // console.log(prettyFormat(f)); + /* * Calculate aliases for try/catch, where any value created * in the try block could be aliased to the catch param @@ -29,7 +39,7 @@ export function inferMutableRanges(ir: HIRFunction): void { * Eagerly canonicalize so that if nothing changes we can bail out * after a single iteration */ - let prevAliases: Map = aliases.canonicalize(); + let prevAliases: Map = aliases.canonicalize(); while (true) { // Infer mutable ranges for aliases that are not fields inferMutableRangesForAlias(ir, aliases); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts index a7e8b5c1f7..528db62c79 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts @@ -5,17 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import { - HIRFunction, - Identifier, - InstructionId, - isRefOrRefValue, -} from '../HIR/HIR'; +import {HIRFunction, InstructionId, isRefOrRefValue, Place} from '../HIR/HIR'; import DisjointSet from '../Utils/DisjointSet'; export function inferMutableRangesForAlias( _fn: HIRFunction, - aliases: DisjointSet, + aliases: DisjointSet, ): void { const aliasSets = aliases.buildSets(); for (const aliasSet of aliasSets) { @@ -24,16 +19,18 @@ export function inferMutableRangesForAlias( * mutated. */ const mutatingIdentifiers = [...aliasSet].filter( - id => - id.mutableRange.end - id.mutableRange.start > 1 && !isRefOrRefValue(id), + place => + place.identifier.mutableRange.end - + place.identifier.mutableRange.start > + 1 && !isRefOrRefValue(place.type), ); if (mutatingIdentifiers.length > 0) { // Find final instruction which mutates this alias set. let lastMutatingInstructionId = 0; - for (const id of mutatingIdentifiers) { - if (id.mutableRange.end > lastMutatingInstructionId) { - lastMutatingInstructionId = id.mutableRange.end; + for (const place of mutatingIdentifiers) { + if (place.identifier.mutableRange.end > lastMutatingInstructionId) { + lastMutatingInstructionId = place.identifier.mutableRange.end; } } @@ -43,10 +40,11 @@ export function inferMutableRangesForAlias( */ for (const alias of aliasSet) { if ( - alias.mutableRange.end < lastMutatingInstructionId && - !isRefOrRefValue(alias) + alias.identifier.mutableRange.end < lastMutatingInstructionId && + !isRefOrRefValue(alias.type) ) { - alias.mutableRange.end = lastMutatingInstructionId as InstructionId; + alias.identifier.mutableRange.end = + lastMutatingInstructionId as InstructionId; } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index 344949b020..dba25b5028 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -14,7 +14,7 @@ import { IdentifierId, Place, computePostDominatorTree, - getHookKind, + getHookKindForType, isStableType, isUseOperator, } from '../HIR'; @@ -204,21 +204,21 @@ export function inferReactivePlaces(fn: HIRFunction): void { */ if ( value.kind === 'CallExpression' && - (getHookKind(fn.env, value.callee.identifier) != null || - isUseOperator(value.callee.identifier)) + (getHookKindForType(fn.env, value.callee.type) != null || + isUseOperator(value.callee.type)) ) { hasReactiveInput = true; } else if ( value.kind === 'MethodCall' && - (getHookKind(fn.env, value.property.identifier) != null || - isUseOperator(value.property.identifier)) + (getHookKindForType(fn.env, value.property.type) != null || + isUseOperator(value.property.type)) ) { hasReactiveInput = true; } if (hasReactiveInput) { for (const lvalue of eachInstructionLValue(instruction)) { - if (isStableType(lvalue.identifier)) { + if (isStableType(lvalue.type)) { continue; } reactiveIdentifiers.markReactive(lvalue); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 8cf30a9666..04fb0f1428 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -478,7 +478,7 @@ class InferenceState { * `expected valueKind to be 'Mutable' but found to be \`${valueKind}\`` * ); */ - effect = isObjectType(place.identifier) ? Effect.Store : Effect.Mutate; + effect = isObjectType(place.type) ? Effect.Store : Effect.Mutate; break; } case Effect.Capture: { @@ -1175,10 +1175,7 @@ function inferBlock( loc: instrValue.loc, }); } - const signature = getFunctionCallSignature( - env, - instrValue.tag.identifier.type, - ); + const signature = getFunctionCallSignature(env, instrValue.tag.type); let calleeEffect = signature?.calleeEffect ?? Effect.ConditionallyMutate; const returnValueKind: AbstractValue = @@ -1209,10 +1206,7 @@ function inferBlock( break; } case 'CallExpression': { - const signature = getFunctionCallSignature( - env, - instrValue.callee.identifier.type, - ); + const signature = getFunctionCallSignature(env, instrValue.callee.type); const effects = signature !== null ? getFunctionEffects(instrValue, signature) : null; @@ -1294,7 +1288,7 @@ function inferBlock( const signature = getFunctionCallSignature( env, - instrValue.property.identifier.type, + instrValue.property.type, ); const returnValueKind: AbstractValue = @@ -1797,7 +1791,7 @@ function inferBlock( kind === ValueKind.Mutable || kind === ValueKind.Context; let effect; let valueKind: AbstractValue; - if (!isMutable || isArrayType(instrValue.collection.identifier)) { + if (!isMutable || isArrayType(instrValue.collection.type)) { // Case 1, assume iterator is a separate mutable object effect = { kind: Effect.Read, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferTryCatchAliases.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferTryCatchAliases.ts index 3b33160820..dd32cbcc30 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferTryCatchAliases.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferTryCatchAliases.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {BlockId, HIRFunction, Identifier} from '../HIR'; +import {BlockId, HIRFunction, Place} from '../HIR'; import DisjointSet from '../Utils/DisjointSet'; /* @@ -16,18 +16,15 @@ import DisjointSet from '../Utils/DisjointSet'; */ export function inferTryCatchAliases( fn: HIRFunction, - aliases: DisjointSet, + aliases: DisjointSet, ): void { - const handlerParams: Map = new Map(); + const handlerParams: Map = new Map(); for (const [_, block] of fn.body.blocks) { if ( block.terminal.kind === 'try' && block.terminal.handlerBinding !== null ) { - handlerParams.set( - block.terminal.handler, - block.terminal.handlerBinding.identifier, - ); + handlerParams.set(block.terminal.handler, block.terminal.handlerBinding); } else if (block.terminal.kind === 'maybe-throw') { const handlerParam = handlerParams.get(block.terminal.handler); if (handlerParam === undefined) { @@ -42,7 +39,7 @@ export function inferTryCatchAliases( * catch clause param */ for (const instr of block.instructions) { - aliases.union([handlerParam, instr.lvalue.identifier]); + aliases.union([handlerParam, instr.lvalue]); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts index d97a4da1ec..c6a05ead1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts @@ -402,6 +402,7 @@ export function inlineJsxTransform( scope.declarations.delete(origId); scope.declarations.set(decl.identifier.id, { identifier: newDecl, + type: decl.type, scope: decl.scope, }); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..34c4d20f0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -46,7 +46,7 @@ export function lowerContextAccess( if ( value.kind === 'CallExpression' && - isUseContextHookType(value.callee.identifier) + isUseContextHookType(value.callee.type) ) { contextAccess.set(lvalue.identifier.id, value); continue; @@ -87,7 +87,7 @@ export function lowerContextAccess( const {lvalue, value} = instr; if ( value.kind === 'CallExpression' && - isUseContextHookType(value.callee.identifier) && + isUseContextHookType(value.callee.type) && contextKeys.has(lvalue.identifier.id) ) { const loweredContextCalleeInstr = emitLoadLoweredContextCallee( diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 167db6dede..f43aaac12a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -41,7 +41,7 @@ import { SourceLocation, SpreadPattern, ValidIdentifierName, - getHookKind, + getHookKindForType, makeIdentifierName, } from '../HIR/HIR'; import {printIdentifier, printPlace} from '../HIR/PrintHIR'; @@ -1687,7 +1687,7 @@ function codegenInstructionValue( } break; } - const isHook = getHookKind(cx.env, instrValue.callee.identifier) != null; + const isHook = getHookKindForType(cx.env, instrValue.callee.type) != null; const callee = codegenPlaceToExpression(cx, instrValue.callee); const args = instrValue.args.map(arg => codegenArgument(cx, arg)); value = createCallExpression( @@ -1751,7 +1751,7 @@ function codegenInstructionValue( } case 'MethodCall': { const isHook = - getHookKind(cx.env, instrValue.property.identifier) != null; + getHookKindForType(cx.env, instrValue.property.type) != null; const memberExpr = codegenPlaceToExpression(cx, instrValue.property); CompilerError.invariant( t.isMemberExpression(memberExpr) || diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts index 3851234005..52da20cb30 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts @@ -14,6 +14,7 @@ import { isPrimitiveType, isUseRefType, Identifier, + Type, } from '../HIR/HIR'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; @@ -53,8 +54,8 @@ class Visitor extends ReactiveFunctionVisitor> { for (const [id, decl] of scopeBlock.scope.declarations) { if ( - !isPrimitiveType(decl.identifier) && - !isStableRefType(decl.identifier, state) + !isPrimitiveType(decl.type) && + !isStableRefType(decl.type, decl.identifier, state) ) { state.add(id); } @@ -62,10 +63,11 @@ class Visitor extends ReactiveFunctionVisitor> { } } function isStableRefType( + type: Type, identifier: Identifier, reactiveIdentifiers: Set, ): boolean { - return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id); + return isUseRefType(type) && !reactiveIdentifiers.has(identifier.id); } /* * Computes a set of identifiers which are reactive, using the analysis previously performed diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts index c7e16cce7a..59680cec80 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts @@ -6,7 +6,12 @@ */ import {CompilerError} from '../CompilerError'; -import {DependencyPath, Identifier, ReactiveScopeDependency} from '../HIR'; +import { + DependencyPath, + Identifier, + makeType, + ReactiveScopeDependency, +} from '../HIR'; import {printIdentifier} from '../HIR/PrintHIR'; import {assertExhaustive} from '../Utils/utils'; @@ -107,6 +112,7 @@ export class ReactiveScopeDependencyTree { for (const dep of deps) { results.add({ identifier: rootId, + type: makeType(), path: dep.relativePath, }); } @@ -121,7 +127,7 @@ export class ReactiveScopeDependencyTree { checkValidDepIdFn: (dep: ReactiveScopeDependency) => boolean, ): void { for (const [id, otherRoot] of depsFromInnerScope.#roots) { - if (!checkValidDepIdFn({identifier: id, path: []})) { + if (!checkValidDepIdFn({identifier: id, type: makeType(), path: []})) { continue; } let currRoot = this.#getOrCreateRoot(id); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts index 103923a2e4..5fe3f05237 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts @@ -11,7 +11,7 @@ import { HIRFunction, LabelTerminal, PrunedScopeTerminal, - getHookKind, + getHookKindForType, isUseOperator, } from '../HIR'; import {retainWhere} from '../Utils/utils'; @@ -52,8 +52,8 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void { const callee = value.kind === 'MethodCall' ? value.property : value.callee; if ( - getHookKind(fn.env, callee.identifier) != null || - isUseOperator(callee.identifier) + getHookKindForType(fn.env, callee.type) != null || + isUseOperator(callee.type) ) { prune.push(...activeScopes.map(entry => entry.block)); activeScopes.length = 0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 098139b150..e9d67b895a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -238,7 +238,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean { case 'TaggedTemplateExpression': case 'CallExpression': case 'MethodCall': { - return instruction.lvalue.identifier.type.kind !== 'Primitive'; + return instruction.lvalue.type.kind !== 'Primitive'; } case 'RegExpLiteral': case 'PropertyStore': diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts index 08d2212d86..54e886619b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts @@ -456,6 +456,7 @@ function canMergeScopes( new Set( [...current.scope.declarations.values()].map(declaration => ({ identifier: declaration.identifier, + type: declaration.type, path: [], })), ), @@ -464,7 +465,7 @@ function canMergeScopes( (next.scope.dependencies.size !== 0 && [...next.scope.dependencies].every( dep => - isAlwaysInvalidatingType(dep.identifier.type) && + isAlwaysInvalidatingType(dep.type) && Iterable_some( current.scope.declarations.values(), decl => @@ -545,6 +546,6 @@ function scopeIsEligibleForMerging(scopeBlock: ReactiveScopeBlock): boolean { return true; } return [...scopeBlock.scope.declarations].some(([, decl]) => - isAlwaysInvalidatingType(decl.identifier.type), + isAlwaysInvalidatingType(decl.type), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts index b5aa44ead0..4c0d4077c3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts @@ -111,8 +111,7 @@ export function writePrunedScope( export function printDependency(dependency: ReactiveScopeDependency): string { const identifier = - printIdentifier(dependency.identifier) + - printType(dependency.identifier.type); + printIdentifier(dependency.identifier) + printType(dependency.type); return `${identifier}${dependency.path.map(token => `${token.optional ? '?.' : '.'}${token.property}`).join('')}`; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts index b8ba196284..d36199edc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts @@ -17,6 +17,7 @@ import { ReactiveStatement, ReactiveTerminalStatement, makeInstructionId, + makeType, promoteTemporary, } from '../HIR'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; @@ -154,6 +155,7 @@ class Transform extends ReactiveFunctionTransform { scopeBlock.scope.earlyReturnValue = earlyReturnValue; scopeBlock.scope.declarations.set(earlyReturnValue.value.id, { identifier: earlyReturnValue.value, + type: makeType(), scope: scopeBlock.scope, }); @@ -239,6 +241,7 @@ class Transform extends ReactiveFunctionTransform { effect: Effect.ConditionallyMutate, loc, reactive: true, + type: makeType(), identifier: earlyReturnValue.value, }, }, @@ -310,6 +313,7 @@ class Transform extends ReactiveFunctionTransform { effect: Effect.Capture, loc, reactive: true, + type: makeType(), }, }, value: stmt.terminal.value, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts index 2a9d0b9793..bec42f98a2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts @@ -8,7 +8,6 @@ import {CompilerError} from '../CompilerError'; import { Environment, - Identifier, IdentifierId, InstructionId, Place, @@ -17,7 +16,8 @@ import { ReactiveInstruction, ReactiveScopeBlock, ReactiveTerminalStatement, - getHookKind, + Type, + getHookKindForType, isUseRefType, isUseStateType, } from '../HIR'; @@ -92,8 +92,8 @@ class Visitor extends ReactiveFunctionVisitor { return values.reduce(join2, 'Unknown'); } - isCreateOnlyHook(id: Identifier): boolean { - return isUseStateType(id) || isUseRefType(id); + isCreateOnlyHook(type: Type): boolean { + return isUseStateType(type) || isUseRefType(type); } override visitPlace( @@ -136,15 +136,17 @@ class Visitor extends ReactiveFunctionVisitor { let callee = null; switch (instruction.value.kind) { case 'CallExpression': { - callee = instruction.value.callee.identifier; + callee = instruction.value.callee; break; } case 'MethodCall': { - callee = instruction.value.property.identifier; + callee = instruction.value.property; break; } } - return callee != null && getHookKind(this.env, callee) != null; + return ( + callee != null && getHookKindForType(this.env, callee.type) != null + ); }; switch (instruction.value.kind) { @@ -152,7 +154,7 @@ class Visitor extends ReactiveFunctionVisitor { case 'MethodCall': { if ( instruction.lvalue && - this.isCreateOnlyHook(instruction.lvalue.identifier) + this.isCreateOnlyHook(instruction.lvalue.type) ) { [...eachCallArgument(instruction.value.args)].forEach(operand => this.visitPlace(instruction.id, operand, 'Create'), diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts index 5a9aa6b2a7..9fefc8b94a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -21,12 +21,11 @@ import { ReactiveTerminalStatement, ReactiveValue, ScopeId, - getHookKind, isMutableEffect, } from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; import {assertExhaustive, getOrInsertDefault} from '../Utils/utils'; -import {getPlaceScope} from '../HIR/HIR'; +import {getHookKindForType, getPlaceScope} from '../HIR/HIR'; import { ReactiveFunctionTransform, ReactiveFunctionVisitor, @@ -672,10 +671,7 @@ function computeMemoizationInputs( }; } case 'TaggedTemplateExpression': { - const signature = getFunctionCallSignature( - env, - value.tag.identifier.type, - ); + const signature = getFunctionCallSignature(env, value.tag.type); let lvalues = []; if (lvalue !== null) { lvalues.push({place: lvalue, level: MemoizationLevel.Memoized}); @@ -698,10 +694,7 @@ function computeMemoizationInputs( }; } case 'CallExpression': { - const signature = getFunctionCallSignature( - env, - value.callee.identifier.type, - ); + const signature = getFunctionCallSignature(env, value.callee.type); let lvalues = []; if (lvalue !== null) { lvalues.push({place: lvalue, level: MemoizationLevel.Memoized}); @@ -724,10 +717,7 @@ function computeMemoizationInputs( }; } case 'MethodCall': { - const signature = getFunctionCallSignature( - env, - value.property.identifier.type, - ); + const signature = getFunctionCallSignature(env, value.property.type); let lvalues = []; if (lvalue !== null) { lvalues.push({place: lvalue, level: MemoizationLevel.Memoized}); @@ -922,11 +912,8 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { instruction.value.kind === 'CallExpression' ? instruction.value.callee : instruction.value.property; - if (getHookKind(state.env, callee.identifier) != null) { - const signature = getFunctionCallSignature( - this.env, - callee.identifier.type, - ); + if (getHookKindForType(state.env, callee.type) != null) { + const signature = getFunctionCallSignature(this.env, callee.type); /* * Hook values are assumed to escape by default since they can be inputs * to reactive scopes in the hook. However if the hook is annotated as diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts index 9bdf15aeb5..d2c2198fdc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts @@ -56,7 +56,7 @@ class Visitor extends ReactiveFunctionVisitor { case 'Destructure': { if (state.has(value.value.identifier.id)) { for (const lvalue of eachPatternOperand(value.lvalue.pattern)) { - if (isStableType(lvalue.identifier)) { + if (isStableType(lvalue.type)) { continue; } state.add(lvalue.identifier.id); @@ -71,7 +71,7 @@ class Visitor extends ReactiveFunctionVisitor { if ( lvalue !== null && state.has(value.object.identifier.id) && - !isStableType(lvalue.identifier) + !isStableType(lvalue.type) ) { state.add(lvalue.identifier.id); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..02852cf2ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -14,7 +14,6 @@ import { Identifier, IdentifierId, makeInstructionId, - makeType, Phi, Place, } from '../HIR/HIR'; @@ -86,7 +85,6 @@ class SSABuilder { end: makeInstructionId(0), }, scope: null, // reset along w the mutable range - type: makeType(), loc: oldId.loc, }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts index 0270723c3e..da58338e9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts @@ -15,6 +15,7 @@ import { IdentifierId, Instruction, makeType, + Place, PropType, Type, typeEquals, @@ -69,17 +70,17 @@ export function inferTypes(func: HIRFunction): void { function apply(func: HIRFunction, unifier: Unifier): void { for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { - phi.place.identifier.type = unifier.get(phi.place.identifier.type); + phi.place.type = unifier.get(phi.place.type); } for (const instr of block.instructions) { for (const operand of eachInstructionLValue(instr)) { - operand.identifier.type = unifier.get(operand.identifier.type); + operand.type = unifier.get(operand.type); } for (const place of eachInstructionOperand(instr)) { - place.identifier.type = unifier.get(place.identifier.type); + place.type = unifier.get(place.type); } const {lvalue, value} = instr; - lvalue.identifier.type = unifier.get(lvalue.identifier.type); + lvalue.type = unifier.get(lvalue.type); if ( value.kind === 'FunctionExpression' || @@ -107,16 +108,26 @@ function equation(left: Type, right: Type): TypeEquation { function* generate( func: HIRFunction, ): Generator { + const typeEnv = new Map(); + function getType(place: Place): Type { + let type = typeEnv.get(place.identifier.id); + if (type === undefined) { + type = place.type; + typeEnv.set(place.identifier.id, type); + } + return type; + } + if (func.fnType === 'Component') { const [props, ref] = func.params; if (props && props.kind === 'Identifier') { - yield equation(props.identifier.type, { + yield equation(getType(props), { kind: 'Object', shapeId: BuiltInPropsId, }); } if (ref && ref.kind === 'Identifier') { - yield equation(ref.identifier.type, { + yield equation(getType(ref), { kind: 'Object', shapeId: BuiltInUseRefId, }); @@ -127,18 +138,23 @@ function* generate( const returnTypes: Array = []; for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { - yield equation(phi.place.identifier.type, { + yield equation(getType(phi.place), { kind: 'Phi', - operands: [...phi.operands.values()].map(id => id.identifier.type), + operands: [...phi.operands.values()].map(id => getType(id)), }); } for (const instr of block.instructions) { - yield* generateInstructionTypes(func.env, names, instr); + yield* generateInstructionTypes( + func.env, + names, + (place: Place) => getType(place), + instr, + ); } const terminal = block.terminal; if (terminal.kind === 'return') { - returnTypes.push(terminal.value.identifier.type); + returnTypes.push(getType(terminal.value)); } } if (returnTypes.length > 1) { @@ -168,10 +184,11 @@ function getName(names: Map, id: IdentifierId): string { function* generateInstructionTypes( env: Environment, names: Map, + getType: (place: Place) => Type, instr: Instruction, ): Generator { const {lvalue, value} = instr; - const left = lvalue.identifier.type; + const left = getType(lvalue); switch (value.kind) { case 'TemplateLiteral': @@ -188,7 +205,7 @@ function* generateInstructionTypes( case 'LoadLocal': { setName(names, lvalue.identifier.id, value.place.identifier); - yield equation(left, value.place.identifier.type); + yield equation(left, getType(value.place)); break; } @@ -201,33 +218,27 @@ function* generateInstructionTypes( case 'StoreLocal': { if (env.config.enableUseTypeAnnotations) { - yield equation( - value.lvalue.place.identifier.type, - value.value.identifier.type, - ); + yield equation(getType(value.lvalue.place), getType(value.value)); const valueType = value.type === null ? makeType() : lowerType(value.type); - yield equation(valueType, value.lvalue.place.identifier.type); + yield equation(valueType, getType(value.lvalue.place)); yield equation(left, valueType); } else { - yield equation(left, value.value.identifier.type); - yield equation( - value.lvalue.place.identifier.type, - value.value.identifier.type, - ); + yield equation(left, getType(value.value)); + yield equation(getType(value.lvalue.place), getType(value.value)); } break; } case 'StoreGlobal': { - yield equation(left, value.value.identifier.type); + yield equation(left, getType(value.value)); break; } case 'BinaryExpression': { if (isPrimitiveBinaryOp(value.operator)) { - yield equation(value.left.identifier.type, {kind: 'Primitive'}); - yield equation(value.right.identifier.type, {kind: 'Primitive'}); + yield equation(getType(value.left), {kind: 'Primitive'}); + yield equation(getType(value.right), {kind: 'Primitive'}); } yield equation(left, {kind: 'Primitive'}); break; @@ -235,8 +246,8 @@ function* generateInstructionTypes( case 'PostfixUpdate': case 'PrefixUpdate': { - yield equation(value.value.identifier.type, {kind: 'Primitive'}); - yield equation(value.lvalue.identifier.type, {kind: 'Primitive'}); + yield equation(getType(value.value), {kind: 'Primitive'}); + yield equation(getType(value.lvalue), {kind: 'Primitive'}); yield equation(left, {kind: 'Primitive'}); break; } @@ -256,7 +267,7 @@ function* generateInstructionTypes( * We should change Hook to a subtype of Function or change unifier logic. * (see https://github.com/facebook/react-forget/pull/1427) */ - yield equation(value.callee.identifier.type, { + yield equation(getType(value.callee), { kind: 'Function', shapeId: null, return: returnType, @@ -272,7 +283,7 @@ function* generateInstructionTypes( * We should change Hook to a subtype of Function or change unifier logic. * (see https://github.com/facebook/react-forget/pull/1427) */ - yield equation(value.tag.identifier.type, { + yield equation(getType(value.tag), { kind: 'Function', shapeId: null, return: returnType, @@ -287,7 +298,7 @@ function* generateInstructionTypes( property.kind === 'ObjectProperty' && property.key.kind === 'computed' ) { - yield equation(property.key.name.identifier.type, { + yield equation(getType(property.key.name), { kind: 'Primitive', }); } @@ -304,7 +315,7 @@ function* generateInstructionTypes( case 'PropertyLoad': { yield equation(left, { kind: 'Property', - objectType: value.object.identifier.type, + objectType: getType(value.object), objectName: getName(names, value.object.identifier.id), propertyName: value.property, }); @@ -313,7 +324,7 @@ function* generateInstructionTypes( case 'MethodCall': { const returnType = makeType(); - yield equation(value.property.identifier.type, { + yield equation(getType(value.property), { kind: 'Function', return: returnType, shapeId: null, @@ -331,9 +342,9 @@ function* generateInstructionTypes( if (item.kind === 'Identifier') { // To simulate tuples we use properties with `String()`, eg "0". const propertyName = String(i); - yield equation(item.identifier.type, { + yield equation(getType(item), { kind: 'Property', - objectType: value.value.identifier.type, + objectType: getType(value.value), objectName: getName(names, value.value.identifier.id), propertyName, }); @@ -348,9 +359,9 @@ function* generateInstructionTypes( property.key.kind === 'identifier' || property.key.kind === 'string' ) { - yield equation(property.place.identifier.type, { + yield equation(getType(property.place), { kind: 'Property', - objectType: value.value.identifier.type, + objectType: getType(value.value), objectName: getName(names, value.value.identifier.id), propertyName: property.key.name, }); @@ -363,10 +374,10 @@ function* generateInstructionTypes( case 'TypeCastExpression': { if (env.config.enableUseTypeAnnotations) { - yield equation(value.type, value.value.identifier.type); + yield equation(value.type, getType(value.value)); yield equation(left, value.type); } else { - yield equation(left, value.value.identifier.type); + yield equation(left, getType(value.value)); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index 53640da502..6f0f55e685 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -18,7 +18,7 @@ import { IdentifierId, Place, SourceLocation, - getHookKind, + getHookKindForType, } from '../HIR/HIR'; import { eachInstructionLValue, @@ -224,7 +224,7 @@ export function validateHooksUsage(fn: HIRFunction): void { * directly a hook, or infer a Global kind from which knownhooks * can be derived later via property access (PropertyLoad etc) */ - if (getHookKind(fn.env, instr.lvalue.identifier) != null) { + if (getHookKindForType(fn.env, instr.lvalue.type) != null) { setKind(instr.lvalue, Kind.KnownHook); } else { setKind(instr.lvalue, Kind.Global); @@ -441,7 +441,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { instr.value.kind === 'CallExpression' ? instr.value.callee : instr.value.property; - const hookKind = getHookKind(fn.env, callee.identifier); + const hookKind = getHookKindForType(fn.env, callee.type); if (hookKind != null) { errors.pushErrorDetail( new CompilerErrorDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 9c41ebcae1..559fc74361 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -148,7 +148,7 @@ function getContextReassignment( if (value.kind === 'CallExpression') { const signature = getFunctionCallSignature( fn.env, - value.callee.identifier.type, + value.callee.type, ); if (signature?.noAlias) { operands = [value.callee]; @@ -156,16 +156,13 @@ function getContextReassignment( } else if (value.kind === 'MethodCall') { const signature = getFunctionCallSignature( fn.env, - value.property.identifier.type, + value.property.type, ); if (signature?.noAlias) { operands = [value.receiver, value.property]; } } else if (value.kind === 'TaggedTemplateExpression') { - const signature = getFunctionCallSignature( - fn.env, - value.tag.identifier.type, - ); + const signature = getFunctionCallSignature(fn.env, value.tag.type); if (signature?.noAlias) { operands = [value.tag]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index 364e78ae6b..a832f06e8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -13,9 +13,7 @@ import { ReactiveInstruction, ReactiveScopeBlock, ScopeId, - isUseEffectHookType, - isUseInsertionEffectHookType, - isUseLayoutEffectHookType, + isEffectHook, } from '../HIR'; import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables'; import { @@ -93,7 +91,7 @@ class Visitor extends ReactiveFunctionVisitor { this.traverseInstruction(instruction, state); if ( instruction.value.kind === 'CallExpression' && - isEffectHook(instruction.value.callee.identifier) && + isEffectHook(instruction.value.callee.type) && instruction.value.args.length >= 2 ) { const deps = instruction.value.args[1]!; @@ -122,11 +120,3 @@ class Visitor extends ReactiveFunctionVisitor { function isUnmemoized(operand: Identifier, scopes: Set): boolean { return operand.scope != null && !scopes.has(operand.scope.id); } - -export function isEffectHook(identifier: Identifier): boolean { - return ( - isUseEffectHookType(identifier) || - isUseLayoutEffectHookType(identifier) || - isUseInsertionEffectHookType(identifier) - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts index b361b2016a..7ba778154a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts @@ -105,9 +105,9 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void { } function refTypeOfType(place: Place): RefAccessType { - if (isRefValueType(place.identifier)) { + if (isRefValueType(place.type)) { return {kind: 'RefValue'}; - } else if (isUseRefType(place.identifier)) { + } else if (isUseRefType(place.type)) { return {kind: 'Ref', refId: nextRefId()}; } else { return {kind: 'None'}; @@ -372,7 +372,7 @@ function validateNoRefAccessInRenderImpl( instr.value.kind === 'CallExpression' ? instr.value.callee : instr.value.property; - const hookKind = getHookKindForType(fn.env, callee.identifier.type); + const hookKind = getHookKindForType(fn.env, callee.type); let returnType: RefAccessType = {kind: 'None'}; const fnType = env.get(callee.identifier.id); if (fnType?.kind === 'Structure' && fnType.fn !== null) { @@ -498,7 +498,7 @@ function validateNoRefAccessInRenderImpl( } if ( - isUseRefType(instr.lvalue.identifier) && + isUseRefType(instr.lvalue.type) && env.get(instr.lvalue.identifier.id)?.kind !== 'Ref' ) { env.set( @@ -510,7 +510,7 @@ function validateNoRefAccessInRenderImpl( ); } if ( - isRefValueType(instr.lvalue.identifier) && + isRefValueType(instr.lvalue.type) && env.get(instr.lvalue.identifier.id)?.kind !== 'RefValue' ) { env.set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts index 2c6e7d8ac6..88253e05f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts @@ -56,7 +56,7 @@ export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void { // faster-path to check if the function expression references a setState [...eachInstructionValueOperand(instr.value)].some( operand => - isSetStateType(operand.identifier) || + isSetStateType(operand.type) || setStateFunctions.has(operand.identifier.id), ) ) { @@ -76,7 +76,7 @@ export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void { instr.value.kind === 'MethodCall' ? instr.value.receiver : instr.value.callee; - if (isUseEffectHookType(callee.identifier)) { + if (isUseEffectHookType(callee.type)) { const arg = instr.value.args[0]; if (arg !== undefined && arg.kind === 'Identifier') { const setState = setStateFunctions.get(arg.identifier.id); @@ -135,7 +135,7 @@ function getSetStateCall( case 'CallExpression': { const callee = instr.value.callee; if ( - isSetStateType(callee.identifier) || + isSetStateType(callee.type) || setStateFunctions.has(callee.identifier.id) ) { /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 3f378b1289..1c51da3ba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -79,7 +79,7 @@ function validateNoSetStateInRenderImpl( // faster-path to check if the function expression references a setState [...eachInstructionValueOperand(instr.value)].some( operand => - isSetStateType(operand.identifier) || + isSetStateType(operand.type) || unconditionalSetStateFunctions.has(operand.identifier.id), ) && // if yes, does it unconditonally call it? @@ -116,7 +116,7 @@ function validateNoSetStateInRenderImpl( case 'CallExpression': { const callee = instr.value.callee; if ( - isSetStateType(callee.identifier) || + isSetStateType(callee.type) || unconditionalSetStateFunctions.has(callee.identifier.id) ) { if (activeManualMemoId !== null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index e7615320c7..65eca99a47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -251,6 +251,7 @@ function validateInferredDep( loc: GeneratedSource, effect: Effect.Read, reactive: false, + type: dep.type, }, }, path: [...dep.path], diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference.js new file mode 100644 index 0000000000..17ff229431 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference.js @@ -0,0 +1,12 @@ +function Component(props) { + useHook(); + const value = makeValue(props.value); + let result; + if (props.cond) { + console.log(value + 1); + result = value; + } else { + result = value.self(); + } + return result; +}