7
8
import {NodePath, Scope} from '@babel/traverse';
9
import * as t from '@babel/types';
10
-import {Expression} from '@babel/types';
10
import invariant from 'invariant';
11
import {
12
CompilerError,
74
parent: NodePath<t.Function> | null = null,
75
): Result<HIRFunction, CompilerError> {
76
const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
78
- const context: Array<Place> = [];
77
+ const context: HIRFunction['context'] = [];
78
79
for (const ref of capturedRefs ?? []) {
80
context.push({
3377
>,
3378
): LoweredFunction | null {
3379
const componentScope: Scope = builder.parentFunction.scope;
3381
- const captured = gatherCapturedDeps(builder, expr, componentScope);
3380
+ const capturedContext = gatherCapturedContext(expr, componentScope);
3381
3382
/*
3383
* TODO(gsn): In the future, we could only pass in the context identifiers
3391
expr,
3392
builder.environment,
3393
builder.bindings,
3395
- [...builder.context, ...captured.identifiers],
3394
+ [...builder.context, ...capturedContext],
3395
builder.parentFunction,
3396
);
3397
let loweredFunc: HIRFunction;
3404
loweredFunc = lowering.unwrap();
3405
return {
3406
func: loweredFunc,
3408
- dependencies: captured.refs,
3407
};
3408
}
3409
4077
}
4078
}
4079
4082
-function isValidDependency(path: NodePath<t.MemberExpression>): boolean {
4083
- const parent: NodePath<t.Node> = path.parentPath;
4084
- return (
4085
- !path.node.computed &&
4086
- !(parent.isCallExpression() && parent.get('callee') === path)
4087
- );
4088
-}
4089
-
4080
function captureScopes({from, to}: {from: Scope; to: Scope}): Set<Scope> {
4081
let scopes: Set<Scope> = new Set();
4082
while (from) {
4091
return scopes;
4092
}
4093
4104
-function gatherCapturedDeps(
4105
- builder: HIRBuilder,
4094
+function gatherCapturedContext(
4095
fn: NodePath<
4096
| t.FunctionExpression
4097
| t.ArrowFunctionExpression
4099
| t.ObjectMethod
4100
>,
4101
componentScope: Scope,
4113
-): {identifiers: Array<t.Identifier>; refs: Array<Place>} {
4114
- const capturedIds: Map<t.Identifier, number> = new Map();
4115
- const capturedRefs: Set<Place> = new Set();
4116
- const seenPaths: Set<string> = new Set();
4102
+): Array<t.Identifier> {
4103
+ const capturedIds = new Set<t.Identifier>();
4104
4105
/*
4106
* Capture all the scopes from the parent of this function up to and including
4111
to: componentScope,
4112
});
4113
4127
- function addCapturedId(bindingIdentifier: t.Identifier): number {
4128
- if (!capturedIds.has(bindingIdentifier)) {
4129
- const index = capturedIds.size;
4130
- capturedIds.set(bindingIdentifier, index);
4131
- return index;
4132
- } else {
4133
- return capturedIds.get(bindingIdentifier)!;
4134
- }
4135
- }
4136
-
4114
function handleMaybeDependency(
4138
- path:
4139
- | NodePath<t.MemberExpression>
4140
- | NodePath<t.Identifier>
4141
- | NodePath<t.JSXOpeningElement>,
4115
+ path: NodePath<t.Identifier> | NodePath<t.JSXOpeningElement>,
4116
): void {
4117
// Base context variable to depend on
4118
let baseIdentifier: NodePath<t.Identifier> | NodePath<t.JSXIdentifier>;
4145
- /*
4146
- * Base expression to depend on, which (for now) may contain non side-effectful
4147
- * member expressions
4148
- */
4149
- let dependency:
4150
- | NodePath<t.MemberExpression>
4151
- | NodePath<t.JSXMemberExpression>
4152
- | NodePath<t.Identifier>
4153
- | NodePath<t.JSXIdentifier>;
4119
if (path.isJSXOpeningElement()) {
4120
const name = path.get('name');
4121
if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) {
4131
'Invalid logic in gatherCapturedDeps',
4132
);
4133
baseIdentifier = current;
4169
-
4170
- /*
4171
- * Get the expression to depend on, which may involve PropertyLoads
4172
- * for member expressions
4173
- */
4174
- let currentDep:
4175
- | NodePath<t.JSXMemberExpression>
4176
- | NodePath<t.Identifier>
4177
- | NodePath<t.JSXIdentifier> = baseIdentifier;
4178
-
4179
- while (true) {
4180
- const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
4181
- if (nextDep && nextDep.isJSXMemberExpression()) {
4182
- currentDep = nextDep;
4183
- } else {
4184
- break;
4185
- }
4186
- }
4187
- dependency = currentDep;
4188
- } else if (path.isMemberExpression()) {
4189
- // Calculate baseIdentifier
4190
- let currentId: NodePath<Expression> = path;
4191
- while (currentId.isMemberExpression()) {
4192
- currentId = currentId.get('object');
4193
- }
4194
- if (!currentId.isIdentifier()) {
4195
- return;
4196
- }
4197
- baseIdentifier = currentId;
4198
-
4199
- /*
4200
- * Get the expression to depend on, which may involve PropertyLoads
4201
- * for member expressions
4202
- */
4203
- let currentDep:
4204
- | NodePath<t.MemberExpression>
4205
- | NodePath<t.Identifier>
4206
- | NodePath<t.JSXIdentifier> = baseIdentifier;
4207
-
4208
- while (true) {
4209
- const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
4210
- if (
4211
- nextDep &&
4212
- nextDep.isMemberExpression() &&
4213
- isValidDependency(nextDep)
4214
- ) {
4215
- currentDep = nextDep;
4216
- } else {
4217
- break;
4218
- }
4219
- }
4220
-
4221
- dependency = currentDep;
4134
} else {
4135
baseIdentifier = path;
4224
- dependency = path;
4136
}
4137
4138
/*
4139
* Skip dependency path, as we already tried to recursively add it (+ all subexpressions)
4140
* as a dependency.
4141
*/
4231
- dependency.skip();
4142
+ path.skip();
4143
4144
// Add the base identifier binding as a dependency.
4145
const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name);
4235
- if (binding === undefined || !pureScopes.has(binding.scope)) {
4236
- return;
4237
- }
4238
- const idKey = String(addCapturedId(binding.identifier));
4239
-
4240
- // Add the expression (potentially a memberexpr path) as a dependency.
4241
- let exprKey = idKey;
4242
- if (dependency.isMemberExpression()) {
4243
- let pathTokens = [];
4244
- let current: NodePath<Expression> = dependency;
4245
- while (current.isMemberExpression()) {
4246
- const property = current.get('property') as NodePath<t.Identifier>;
4247
- pathTokens.push(property.node.name);
4248
- current = current.get('object');
4249
- }
4250
-
4251
- exprKey += '.' + pathTokens.reverse().join('.');
4252
- } else if (dependency.isJSXMemberExpression()) {
4253
- let pathTokens = [];
4254
- let current: NodePath<t.JSXMemberExpression | t.JSXIdentifier> =
4255
- dependency;
4256
- while (current.isJSXMemberExpression()) {
4257
- const property = current.get('property');
4258
- pathTokens.push(property.node.name);
4259
- current = current.get('object');
4260
- }
4261
- }
4262
-
4263
- if (!seenPaths.has(exprKey)) {
4264
- let loweredDep: Place;
4265
- if (dependency.isJSXIdentifier()) {
4266
- loweredDep = lowerValueToTemporary(builder, {
4267
- kind: 'LoadLocal',
4268
- place: lowerIdentifier(builder, dependency),
4269
- loc: path.node.loc ?? GeneratedSource,
4270
- });
4271
- } else if (dependency.isJSXMemberExpression()) {
4272
- loweredDep = lowerJsxMemberExpression(builder, dependency);
4273
- } else {
4274
- loweredDep = lowerExpressionToTemporary(builder, dependency);
4275
- }
4276
- capturedRefs.add(loweredDep);
4277
- seenPaths.add(exprKey);
4146
+ if (binding !== undefined && pureScopes.has(binding.scope)) {
4147
+ capturedIds.add(binding.identifier);
4148
}
4149
}
4150
4175
return;
4176
} else if (path.isJSXElement()) {
4177
handleMaybeDependency(path.get('openingElement'));
4308
- } else if (path.isMemberExpression() || path.isIdentifier()) {
4178
+ } else if (path.isIdentifier()) {
4179
handleMaybeDependency(path);
4180
}
4181
},
4182
});
4183
4314
- return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]};
4184
+ return [...capturedIds.keys()];
4185
}
4186
4187
function notNull<T>(value: T | null): value is T {