Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
codegenFunction,
extractScopeDeclarationsFromDestructuring,
inferReactiveScopeVariables,
markNonReactiveScopes,
memoizeFbtAndMacroOperandsInSameScope,
mergeReactiveScopesThatInvalidateTogether,
promoteUsedTemporaries,
Expand Down Expand Up @@ -460,6 +461,13 @@ function runWithEnvironment(

assertWellFormedBreakTargets(reactiveFunction);

markNonReactiveScopes(reactiveFunction);
log({
kind: 'reactive',
name: 'MarkNonReactiveScopes',
value: reactiveFunction,
});

pruneUnusedLabels(reactiveFunction);
log({
kind: 'reactive',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import {
validateIdentifierName,
} from './HIR';
import HIRBuilder, {Bindings, createTemporaryPlace} from './HIRBuilder';
import {BuiltInArrayId} from './ObjectShape';
import {BuiltInArrayId, BuiltInNonReactiveId} from './ObjectShape';

/*
* *******************************************************************************************
Expand Down Expand Up @@ -184,6 +184,67 @@ export function lower(
}
});

// Extract per-property type annotations for NonReactive props
let propsTypeAnnotations: Map<string, t.FlowType | t.TSType> | null = null;
if (env.config.enableNonReactiveAnnotation) {
const firstParam = func.get('params')[0];
if (firstParam != null && firstParam.isObjectPattern()) {
const typeAnnotation = (firstParam.node as t.ObjectPattern).typeAnnotation;
if (typeAnnotation != null) {
const annoNode =
typeAnnotation.type === 'TSTypeAnnotation'
? typeAnnotation.typeAnnotation
: typeAnnotation.type === 'TypeAnnotation'
? typeAnnotation.typeAnnotation
: null;
if (annoNode != null) {
const members =
annoNode.type === 'TSTypeLiteral'
? annoNode.members
: annoNode.type === 'ObjectTypeAnnotation'
? annoNode.properties
: null;
if (members != null) {
for (const member of members) {
let propName: string | null = null;
let propType: t.FlowType | t.TSType | null = null;
if (
member.type === 'TSPropertySignature' &&
member.key.type === 'Identifier' &&
member.typeAnnotation?.type === 'TSTypeAnnotation'
) {
const tsType = member.typeAnnotation.typeAnnotation;
if (
tsType.type === 'TSTypeReference' &&
tsType.typeName.type === 'Identifier' &&
tsType.typeName.name === 'NonReactive'
) {
propName = member.key.name;
propType = tsType;
}
} else if (
member.type === 'ObjectTypeProperty' &&
member.key.type === 'Identifier' &&
member.value.type === 'GenericTypeAnnotation' &&
member.value.id.type === 'Identifier' &&
member.value.id.name === 'NonReactive'
) {
propName = member.key.name;
propType = member.value;
}
if (propName != null && propType != null) {
if (propsTypeAnnotations == null) {
propsTypeAnnotations = new Map();
}
propsTypeAnnotations.set(propName, propType);
}
}
}
}
}
}
}

let directives: Array<string> = [];
const body = func.get('body');
if (body.isExpression()) {
Expand Down Expand Up @@ -260,6 +321,7 @@ export function lower(
effects: null,
aliasingEffects: null,
directives,
propsTypeAnnotations,
});
}

Expand Down Expand Up @@ -4349,13 +4411,29 @@ export function lowerType(node: t.FlowType | t.TSType): Type {
if (id.type === 'Identifier' && id.name === 'Array') {
return {kind: 'Object', shapeId: BuiltInArrayId};
}
if (id.type === 'Identifier' && id.name === 'NonReactive') {
return {
kind: 'Function',
shapeId: BuiltInNonReactiveId,
return: makeType(),
isConstructor: false,
};
}
return makeType();
}
case 'TSTypeReference': {
const typeName = node.typeName;
if (typeName.type === 'Identifier' && typeName.name === 'Array') {
return {kind: 'Object', shapeId: BuiltInArrayId};
}
if (typeName.type === 'Identifier' && typeName.name === 'NonReactive') {
return {
kind: 'Function',
shapeId: BuiltInNonReactiveId,
return: makeType(),
isConstructor: false,
};
}
return makeType();
}
case 'ArrayTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,15 @@ export const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),

/**
* Enable support for the NonReactive<T> type annotation. When enabled,
* props annotated as NonReactive are treated as stable (non-reactive) and
* safe for ref access. Local functions annotated as NonReactive are compiled
* using a two-slot pattern that produces a stable function identity.
* Requires enableUseTypeAnnotations to also be enabled.
*/
enableNonReactiveAnnotation: z.boolean().default(false),

/**
* Allows specifying a function that can populate HIR with type information from
* Flow
Expand Down
17 changes: 16 additions & 1 deletion compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ export type HIRFunction = {
async: boolean;
directives: Array<string>;
aliasingEffects: Array<AliasingEffect> | null;
propsTypeAnnotations: Map<string, t.FlowType | t.TSType> | null;
};

/*
Expand Down Expand Up @@ -1620,6 +1621,13 @@ export type ReactiveScope = {
merged: Set<ScopeId>;

loc: SourceLocation;

/**
* When true, this scope contains a NonReactive function that should use
* the two-slot codegen pattern: one slot always holds the latest function,
* another slot holds a stable wrapper created once on first render.
*/
nonReactive: boolean;
};

export type ReactiveScopeDependencies = Set<ReactiveScopeDependency>;
Expand Down Expand Up @@ -1899,14 +1907,21 @@ export function isEffectEventFunctionType(id: Identifier): boolean {
);
}

export function isNonReactiveType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInNonReactive'
);
}

export function isStableType(id: Identifier): boolean {
return (
isSetStateType(id) ||
isSetActionStateType(id) ||
isDispatcherType(id) ||
isUseRefType(id) ||
isStartTransitionType(id) ||
isSetOptimisticType(id)
isSetOptimisticType(id) ||
isNonReactiveType(id)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export const BuiltInUseEffectEventId = 'BuiltInUseEffectEvent';
export const BuiltInEffectEventId = 'BuiltInEffectEventFunction';
export const BuiltInAutodepsId = 'BuiltInAutoDepsId';
export const BuiltInEventHandlerId = 'BuiltInEventHandlerId';
export const BuiltInNonReactiveId = 'BuiltInNonReactive';

// See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types
export const ReanimatedSharedValueId = 'ReanimatedSharedValueId';
Expand Down Expand Up @@ -1262,6 +1263,19 @@ addFunction(
BuiltInEventHandlerId,
);

addFunction(
BUILTIN_SHAPES,
[],
{
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: {kind: 'Poly'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
},
BuiltInNonReactiveId,
);

/**
* MixedReadOnly =
* | primitive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
BasicBlock,
BlockId,
isEffectEventFunctionType,
isNonReactiveType,
} from '../HIR';
import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
Expand Down Expand Up @@ -226,7 +227,8 @@ export function inferEffectDependencies(fn: HIRFunction): void {
isSetStateType(maybeDep.identifier)) &&
!reactiveIds.has(maybeDep.identifier.id)) ||
isFireFunctionType(maybeDep.identifier) ||
isEffectEventFunctionType(maybeDep.identifier)
isEffectEventFunctionType(maybeDep.identifier) ||
isNonReactiveType(maybeDep.identifier)
) {
// exclude non-reactive hook results, which will never be in a memo block
continue;
Expand Down Expand Up @@ -614,6 +616,7 @@ function inferDependencies(
earlyReturnValue: null,
merged: new Set(),
loc: GeneratedSource,
nonReactive: false,
};
context.enterScope(placeholderScope);
inferDependenciesInFn(fn, context, temporaries);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Place,
evaluatesToStableTypeOrContainer,
getHookKind,
isNonReactiveType,
isStableType,
isStableTypeContainer,
isUseOperator,
Expand Down Expand Up @@ -96,6 +97,15 @@ class StableSidemap {
});
}
}
} else {
// NonReactive-typed identifiers are stable regardless of source
for (const lvalue of eachInstructionLValue(instr)) {
if (isNonReactiveType(lvalue.identifier)) {
this.map.set(lvalue.identifier.id, {
isStable: true,
});
}
}
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
async: false,
directives: [],
aliasingEffects: [],
propsTypeAnnotations: null,
};

reversePostorderBlocks(fn.body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ function emitOutlinedFn(
async: false,
directives: [],
aliasingEffects: [],
propsTypeAnnotations: null,
};
return fn;
}
Expand Down
Loading