mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler][nocommit] Quick sketch of types on places
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<Identifier>` and checks the types for some things (refs in particular). So the obvious approach is to replace that with a `DisjointSet<Place>`. 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: 8360182e32
Pull Request resolved: https://github.com/facebook/react/pull/31575
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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') {
|
||||
|
||||
+3
@@ -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,
|
||||
{
|
||||
|
||||
+2
-1
@@ -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<ReactiveScopeDependency>,
|
||||
): 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(
|
||||
|
||||
@@ -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<ReactiveScopeDependency>;
|
||||
|
||||
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<DependencyPathEntry>;
|
||||
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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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('');
|
||||
|
||||
+15
-4
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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<Identifier>;
|
||||
|
||||
export function inferAliases(func: HIRFunction): DisjointSet<Identifier> {
|
||||
const aliases = new DisjointSet<Identifier>();
|
||||
export function inferAliases(func: HIRFunction): DisjointSet<Place> {
|
||||
const aliases = new DisjointSet<Place>();
|
||||
const declarations = new Map<IdentifierId, Place>();
|
||||
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<IdentifierId, Place>, 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<Identifier>,
|
||||
aliases: DisjointSet<Place>,
|
||||
declarations: Map<IdentifierId, Place>,
|
||||
): 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Identifier>,
|
||||
aliases: DisjointSet<Place>,
|
||||
): 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Identifier>,
|
||||
aliases: DisjointSet<Place>,
|
||||
): 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<Identifier>,
|
||||
aliases: DisjointSet<Place>,
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
/*
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
@@ -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<Identifier, Identifier> = aliases.canonicalize();
|
||||
let prevAliases: Map<Place, Place> = aliases.canonicalize();
|
||||
while (true) {
|
||||
// Infer mutable ranges for aliases that are not fields
|
||||
inferMutableRangesForAlias(ir, aliases);
|
||||
|
||||
+13
-15
@@ -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<Identifier>,
|
||||
aliases: DisjointSet<Place>,
|
||||
): 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+5
-11
@@ -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,
|
||||
|
||||
@@ -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<Identifier>,
|
||||
aliases: DisjointSet<Place>,
|
||||
): void {
|
||||
const handlerParams: Map<BlockId, Identifier> = new Map();
|
||||
const handlerParams: Map<BlockId, Place> = 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -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(
|
||||
|
||||
+3
-3
@@ -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) ||
|
||||
|
||||
+5
-3
@@ -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<Set<IdentifierId>> {
|
||||
|
||||
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<Set<IdentifierId>> {
|
||||
}
|
||||
}
|
||||
function isStableRefType(
|
||||
type: Type,
|
||||
identifier: Identifier,
|
||||
reactiveIdentifiers: Set<IdentifierId>,
|
||||
): 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
|
||||
|
||||
+8
-2
@@ -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);
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
+1
-1
@@ -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':
|
||||
|
||||
+3
-2
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
+1
-2
@@ -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('')}`;
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -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<State> {
|
||||
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<State> {
|
||||
effect: Effect.ConditionallyMutate,
|
||||
loc,
|
||||
reactive: true,
|
||||
type: makeType(),
|
||||
identifier: earlyReturnValue.value,
|
||||
},
|
||||
},
|
||||
@@ -310,6 +313,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
effect: Effect.Capture,
|
||||
loc,
|
||||
reactive: true,
|
||||
type: makeType(),
|
||||
},
|
||||
},
|
||||
value: stmt.terminal.value,
|
||||
|
||||
+10
-8
@@ -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<CreateUpdate> {
|
||||
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<CreateUpdate> {
|
||||
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<CreateUpdate> {
|
||||
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'),
|
||||
|
||||
+6
-19
@@ -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<State> {
|
||||
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
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
|
||||
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<ReactiveIdentifiers> {
|
||||
if (
|
||||
lvalue !== null &&
|
||||
state.has(value.object.identifier.id) &&
|
||||
!isStableType(lvalue.identifier)
|
||||
!isStableType(lvalue.type)
|
||||
) {
|
||||
state.add(lvalue.identifier.id);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<TypeEquation, void, undefined> {
|
||||
const typeEnv = new Map<IdentifierId, Type>();
|
||||
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<Type> = [];
|
||||
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<IdentifierId, string>, id: IdentifierId): string {
|
||||
function* generateInstructionTypes(
|
||||
env: Environment,
|
||||
names: Map<IdentifierId, string>,
|
||||
getType: (place: Place) => Type,
|
||||
instr: Instruction,
|
||||
): Generator<TypeEquation, void, undefined> {
|
||||
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(<index>)`, 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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
+3
-6
@@ -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];
|
||||
}
|
||||
|
||||
+2
-12
@@ -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<CompilerError> {
|
||||
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<CompilerError> {
|
||||
function isUnmemoized(operand: Identifier, scopes: Set<ScopeId>): boolean {
|
||||
return operand.scope != null && !scopes.has(operand.scope.id);
|
||||
}
|
||||
|
||||
export function isEffectHook(identifier: Identifier): boolean {
|
||||
return (
|
||||
isUseEffectHookType(identifier) ||
|
||||
isUseLayoutEffectHookType(identifier) ||
|
||||
isUseInsertionEffectHookType(identifier)
|
||||
);
|
||||
}
|
||||
|
||||
+5
-5
@@ -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(
|
||||
|
||||
+3
-3
@@ -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)
|
||||
) {
|
||||
/*
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+1
@@ -251,6 +251,7 @@ function validateInferredDep(
|
||||
loc: GeneratedSource,
|
||||
effect: Effect.Read,
|
||||
reactive: false,
|
||||
type: dep.type,
|
||||
},
|
||||
},
|
||||
path: [...dep.path],
|
||||
|
||||
+12
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user