octane 0.1.30 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/compile.js +283 -26
- package/dist/runtime.js +1 -0
- package/package.json +1 -1
package/dist/compiler/compile.js
CHANGED
|
@@ -3466,9 +3466,11 @@ function containsComponentCallOrControlFlow(stmts) {
|
|
|
3466
3466
|
}
|
|
3467
3467
|
|
|
3468
3468
|
/**
|
|
3469
|
-
* A host-only conditional can share its
|
|
3470
|
-
* proof,
|
|
3471
|
-
*
|
|
3469
|
+
* A host-only conditional can share its containing row's immutable
|
|
3470
|
+
* item/dependency proof, including through a narrowly proven nested keyed list.
|
|
3471
|
+
* Entering either construct still needs the row's active scope. Lists without a
|
|
3472
|
+
* conditional, components, opaque list expressions, and every other
|
|
3473
|
+
* control-flow boundary remain on their existing path.
|
|
3472
3474
|
*/
|
|
3473
3475
|
function hasOnlyHostConditionalItemBodies(stmts) {
|
|
3474
3476
|
let hasConditional = false;
|
|
@@ -3497,6 +3499,28 @@ function hasOnlyHostConditionalItemBodies(stmts) {
|
|
|
3497
3499
|
}
|
|
3498
3500
|
if (type === 'IfStatement' || type === 'JSXIfExpression') {
|
|
3499
3501
|
hasConditional = true;
|
|
3502
|
+
} else if (type === 'JSXForExpression') {
|
|
3503
|
+
const declaration = node.left;
|
|
3504
|
+
const item = declaration?.declarations?.[0]?.id;
|
|
3505
|
+
const key = unwrapTsExpr(node.key);
|
|
3506
|
+
if (
|
|
3507
|
+
node.await === true ||
|
|
3508
|
+
node.index != null ||
|
|
3509
|
+
node.nativeArrayMap !== undefined ||
|
|
3510
|
+
declaration?.type !== 'VariableDeclaration' ||
|
|
3511
|
+
declaration.declarations?.length !== 1 ||
|
|
3512
|
+
item?.type !== 'Identifier' ||
|
|
3513
|
+
!isAutoMemoCalculationDependency(node.right) ||
|
|
3514
|
+
key?.type !== 'MemberExpression' ||
|
|
3515
|
+
key.computed === true ||
|
|
3516
|
+
key.optional === true ||
|
|
3517
|
+
key.object?.type !== 'Identifier' ||
|
|
3518
|
+
key.object.name !== item.name ||
|
|
3519
|
+
key.property?.type !== 'Identifier'
|
|
3520
|
+
) {
|
|
3521
|
+
disallowed = true;
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3500
3524
|
} else if (
|
|
3501
3525
|
type === 'ForStatement' ||
|
|
3502
3526
|
type === 'ForInStatement' ||
|
|
@@ -3506,7 +3530,6 @@ function hasOnlyHostConditionalItemBodies(stmts) {
|
|
|
3506
3530
|
type === 'TryStatement' ||
|
|
3507
3531
|
type === 'SwitchStatement' ||
|
|
3508
3532
|
type === 'ActivityStatement' ||
|
|
3509
|
-
type === 'JSXForExpression' ||
|
|
3510
3533
|
type === 'JSXTryExpression' ||
|
|
3511
3534
|
type === 'JSXSwitchExpression' ||
|
|
3512
3535
|
type === 'JSXActivityExpression' ||
|
|
@@ -4144,11 +4167,12 @@ function containsDeferredRefRead(root) {
|
|
|
4144
4167
|
return found;
|
|
4145
4168
|
}
|
|
4146
4169
|
|
|
4147
|
-
//
|
|
4148
|
-
//
|
|
4170
|
+
// Imported component tags rendered anywhere in the body — the auto-memo
|
|
4171
|
+
// region's runtime witnesses. (Deferred-ref reads are no longer collected
|
|
4172
|
+
// body-wide here: they gate per call site and per local through
|
|
4173
|
+
// collectAutoMemoLocalHazards.)
|
|
4149
4174
|
function scanComponentBody(root, importedNames) {
|
|
4150
4175
|
const importedComponents = new Set();
|
|
4151
|
-
let readsDeferredRef = false;
|
|
4152
4176
|
const seen = new WeakSet();
|
|
4153
4177
|
function walk(node) {
|
|
4154
4178
|
if (!node || typeof node !== 'object') return;
|
|
@@ -4158,7 +4182,6 @@ function scanComponentBody(root, importedNames) {
|
|
|
4158
4182
|
}
|
|
4159
4183
|
if (seen.has(node)) return;
|
|
4160
4184
|
seen.add(node);
|
|
4161
|
-
if (!readsDeferredRef && isDeferredRefRead(node)) readsDeferredRef = true;
|
|
4162
4185
|
const imported = importedComponentTag(node, importedNames);
|
|
4163
4186
|
if (imported !== null) importedComponents.add(imported);
|
|
4164
4187
|
for (const key in node) {
|
|
@@ -4167,7 +4190,7 @@ function scanComponentBody(root, importedNames) {
|
|
|
4167
4190
|
}
|
|
4168
4191
|
}
|
|
4169
4192
|
walk(root);
|
|
4170
|
-
return { importedComponents
|
|
4193
|
+
return { importedComponents };
|
|
4171
4194
|
}
|
|
4172
4195
|
|
|
4173
4196
|
// A JSX member chain bottoms out at a JSXIdentifier, so the base test below
|
|
@@ -4203,9 +4226,137 @@ function containsImportedMemberRead(root, importedNames) {
|
|
|
4203
4226
|
return found;
|
|
4204
4227
|
}
|
|
4205
4228
|
|
|
4229
|
+
/**
|
|
4230
|
+
* Component locals whose values a region's dependency tuple cannot witness.
|
|
4231
|
+
*
|
|
4232
|
+
* A region dep on a LOCAL compares the local's per-render value. Locals
|
|
4233
|
+
* re-initialize on every body entry, so a plain value local — however it is
|
|
4234
|
+
* conditionally reassigned during render — is always an exact witness of
|
|
4235
|
+
* itself. What it CANNOT witness is a value drawn from mutable state whose
|
|
4236
|
+
* container identity hides the drift:
|
|
4237
|
+
*
|
|
4238
|
+
* - a ref read (`.current` / any computed member — `isDeferredRefRead`'s
|
|
4239
|
+
* definition): ref contents mutate outside render without scheduling one,
|
|
4240
|
+
* and a stable-identity closure (`useCallback(() => ref.current, [])`)
|
|
4241
|
+
* hands a skipped subtree a LIVE read whose changes nothing witnesses —
|
|
4242
|
+
* so the classification descends into nested function values;
|
|
4243
|
+
* - an imported binding's member read: a live import's property can move
|
|
4244
|
+
* while the import's identity (the only thing a dep can hold) stays
|
|
4245
|
+
* fixed.
|
|
4246
|
+
*
|
|
4247
|
+
* A name is tainted when such a read reaches its value through ANY feed —
|
|
4248
|
+
* declaration initializers at every nesting depth, destructuring patterns
|
|
4249
|
+
* that carry the read themselves (`const { current: el } = ref`, a default or
|
|
4250
|
+
* computed key reading a ref or live import), assignment right-hand sides, or
|
|
4251
|
+
* a for-of/for-in source — directly or through another tainted local (fixed
|
|
4252
|
+
* point, so declaration order and arrow-hoisted back-references cannot hide a
|
|
4253
|
+
* hazard). Nested and loop-scoped names are not component locals, so tainting
|
|
4254
|
+
* them is inert at call sites directly (unknown names already fail closed),
|
|
4255
|
+
* but it is what lets a hazard PROPAGATE through them into a top-level local
|
|
4256
|
+
* (`let out; for (const x of ref.current.items) out = x`). Shadowing is
|
|
4257
|
+
* folded by name — an inner binding's hazard taints its outer namesake —
|
|
4258
|
+
* which only ever declines more. Props params and same-module function
|
|
4259
|
+
* declarations never taint: the former are the witnessed snapshot itself, the
|
|
4260
|
+
* latter are immutable identities whose render-time CALLS the per-site
|
|
4261
|
+
* render-call gate already rejects.
|
|
4262
|
+
*
|
|
4263
|
+
* The direct forms of these reads inside a call site's own props are rejected
|
|
4264
|
+
* per site (containsAutoMemoUnsafeStructure / containsImportedMemberRead);
|
|
4265
|
+
* this set covers the same reads laundered through a local.
|
|
4266
|
+
*/
|
|
4267
|
+
function collectAutoMemoLocalHazards(stmts, importedNames) {
|
|
4268
|
+
const hazards = new Set();
|
|
4269
|
+
// name -> union of free identifiers across its clean value sources; a name
|
|
4270
|
+
// with a directly hazardous source moves to `hazards` and leaves this map.
|
|
4271
|
+
const sourceFrees = new Map();
|
|
4272
|
+
const feed = (pattern, rhs) => {
|
|
4273
|
+
if (!rhs) return;
|
|
4274
|
+
const names = new Set();
|
|
4275
|
+
collectBindings(pattern, names);
|
|
4276
|
+
if (names.size === 0) return;
|
|
4277
|
+
// The pattern side can carry the read itself: a `current`/computed
|
|
4278
|
+
// binding key destructures ref contents off the source, and a default's
|
|
4279
|
+
// expression evaluates at destructure time. Both walks descend the whole
|
|
4280
|
+
// pattern, so nested shapes and defaults are covered together.
|
|
4281
|
+
const direct =
|
|
4282
|
+
containsDeferredRefRead(rhs) ||
|
|
4283
|
+
containsImportedMemberRead(rhs, importedNames) ||
|
|
4284
|
+
containsDeferredRefRead(pattern) ||
|
|
4285
|
+
containsImportedMemberRead(pattern, importedNames);
|
|
4286
|
+
// Default expressions inside the pattern read values too; their free
|
|
4287
|
+
// identifiers (which include the pattern's own bound names — inert
|
|
4288
|
+
// self-loops) join the clean-source union.
|
|
4289
|
+
const frees = direct
|
|
4290
|
+
? null
|
|
4291
|
+
: [...collectFreeIdentifiers(rhs, []), ...collectFreeIdentifiers(pattern, [])];
|
|
4292
|
+
for (const name of names) {
|
|
4293
|
+
if (hazards.has(name)) continue;
|
|
4294
|
+
if (direct) {
|
|
4295
|
+
hazards.add(name);
|
|
4296
|
+
sourceFrees.delete(name);
|
|
4297
|
+
continue;
|
|
4298
|
+
}
|
|
4299
|
+
let set = sourceFrees.get(name);
|
|
4300
|
+
if (set === undefined) sourceFrees.set(name, (set = new Set()));
|
|
4301
|
+
for (const id of frees) set.add(id);
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
// One walk feeds every binding form at every depth, including inside
|
|
4305
|
+
// nested function values: a deferred write targets a PREVIOUS render's
|
|
4306
|
+
// binding and can never leak into the next render's dep snapshot, but
|
|
4307
|
+
// walking uniformly is cheaper than proving which writes are deferred, and
|
|
4308
|
+
// only hazardous sources taint anyway.
|
|
4309
|
+
const seen = new WeakSet();
|
|
4310
|
+
(function walk(n) {
|
|
4311
|
+
if (!n) return;
|
|
4312
|
+
if (Array.isArray(n)) {
|
|
4313
|
+
for (const x of n) walk(x);
|
|
4314
|
+
return;
|
|
4315
|
+
}
|
|
4316
|
+
if (typeof n !== 'object' || !n.type || seen.has(n)) return;
|
|
4317
|
+
seen.add(n);
|
|
4318
|
+
if (n.type === 'VariableDeclaration') {
|
|
4319
|
+
for (const d of n.declarations || []) feed(d.id, d.init);
|
|
4320
|
+
} else if (n.type === 'AssignmentExpression' && n.left?.type !== 'MemberExpression') {
|
|
4321
|
+
feed(n.left, n.right);
|
|
4322
|
+
} else if ((n.type === 'ForOfStatement' || n.type === 'ForInStatement') && n.left) {
|
|
4323
|
+
// The loop source feeds the bound names whether the left declares
|
|
4324
|
+
// (`for (const x of src)` — declarator inits are null, so the
|
|
4325
|
+
// declaration arm above cannot see src) or reuses an outer binding.
|
|
4326
|
+
if (n.left.type === 'VariableDeclaration') {
|
|
4327
|
+
for (const d of n.left.declarations || []) feed(d.id, n.right);
|
|
4328
|
+
} else {
|
|
4329
|
+
feed(n.left, n.right);
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
for (const key in n) {
|
|
4333
|
+
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
4334
|
+
walk(n[key]);
|
|
4335
|
+
}
|
|
4336
|
+
})(stmts);
|
|
4337
|
+
let changed = true;
|
|
4338
|
+
while (changed) {
|
|
4339
|
+
changed = false;
|
|
4340
|
+
for (const [name, free] of sourceFrees) {
|
|
4341
|
+
for (const id of free) {
|
|
4342
|
+
if (!hazards.has(id)) continue;
|
|
4343
|
+
hazards.add(name);
|
|
4344
|
+
sourceFrees.delete(name);
|
|
4345
|
+
changed = true;
|
|
4346
|
+
break;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
return hazards;
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4206
4353
|
function containsAutoMemoUnsafeStructure(stmts) {
|
|
4207
4354
|
let found = false;
|
|
4208
4355
|
const seen = new WeakSet();
|
|
4356
|
+
// Spread bags on HOST elements, marked admissible by the owning element's
|
|
4357
|
+
// visit below. Parents walk before their attributes, so membership is
|
|
4358
|
+
// decided before the spread node itself is reached.
|
|
4359
|
+
const hostSpreads = new WeakSet();
|
|
4209
4360
|
function walk(n) {
|
|
4210
4361
|
if (found || !n) return;
|
|
4211
4362
|
if (Array.isArray(n)) {
|
|
@@ -4237,13 +4388,29 @@ function containsAutoMemoUnsafeStructure(stmts) {
|
|
|
4237
4388
|
t === 'YieldExpression' ||
|
|
4238
4389
|
t === 'ThrowStatement' ||
|
|
4239
4390
|
t === 'TryStatement' ||
|
|
4240
|
-
t === 'JSXTryExpression'
|
|
4241
|
-
t === 'SpreadAttribute' ||
|
|
4242
|
-
t === 'JSXSpreadAttribute'
|
|
4391
|
+
t === 'JSXTryExpression'
|
|
4243
4392
|
) {
|
|
4244
4393
|
found = true;
|
|
4245
4394
|
return;
|
|
4246
4395
|
}
|
|
4396
|
+
if (t === 'SpreadAttribute' || t === 'JSXSpreadAttribute') {
|
|
4397
|
+
// A HOST element's spread bag is one setSpread binding re-diffed on every
|
|
4398
|
+
// region entry. Its argument is composed of deps the region guard already
|
|
4399
|
+
// witnesses (unwitnessed names fail closed independently), so a skip is
|
|
4400
|
+
// the no-op a re-entry would have been — the same immutable-snapshot
|
|
4401
|
+
// argument that admits `class={e.cls}` member reads; runtime keys the
|
|
4402
|
+
// walk cannot see (ref/on*/value) diff per entry with full parity to
|
|
4403
|
+
// their direct-attribute forms. A COMPONENT tag's spread instead builds
|
|
4404
|
+
// the child's props snapshot (getter evaluation, hidden prop names) and
|
|
4405
|
+
// stays rejected, matching the callSiteOk exclusion — which
|
|
4406
|
+
// isAutoMemoPropsParam's rest admission relies on. The argument still
|
|
4407
|
+
// walks below: accessors, computed members, and ref reads inside it keep
|
|
4408
|
+
// failing closed.
|
|
4409
|
+
if (!hostSpreads.has(n)) {
|
|
4410
|
+
found = true;
|
|
4411
|
+
return;
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4247
4414
|
if (t === 'UnaryExpression' && n.operator === 'delete') {
|
|
4248
4415
|
found = true;
|
|
4249
4416
|
return;
|
|
@@ -4311,6 +4478,13 @@ function containsAutoMemoUnsafeStructure(stmts) {
|
|
|
4311
4478
|
found = true;
|
|
4312
4479
|
return;
|
|
4313
4480
|
}
|
|
4481
|
+
if (!isComponentTag(n)) {
|
|
4482
|
+
for (const attr of n.attributes || n.openingElement?.attributes || []) {
|
|
4483
|
+
if (attr.type === 'SpreadAttribute' || attr.type === 'JSXSpreadAttribute') {
|
|
4484
|
+
hostSpreads.add(attr);
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4314
4488
|
}
|
|
4315
4489
|
for (const key in n) {
|
|
4316
4490
|
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
@@ -4321,6 +4495,54 @@ function containsAutoMemoUnsafeStructure(stmts) {
|
|
|
4321
4495
|
return found;
|
|
4322
4496
|
}
|
|
4323
4497
|
|
|
4498
|
+
/**
|
|
4499
|
+
* Can this props parameter stand in for autoMemo's "one ordinary props
|
|
4500
|
+
* snapshot"? A bare Identifier always can. A destructuring ObjectPattern can
|
|
4501
|
+
* when destructuring it evaluates NO expressions and reads NOTHING mutable:
|
|
4502
|
+
* every binding it introduces is already a component local
|
|
4503
|
+
* (collectComponentLocals walks patterns), so the body's free-identifier and
|
|
4504
|
+
* capture analysis is complete for it — the pattern itself is the only part
|
|
4505
|
+
* those walks never see, because they take the body root, not the params.
|
|
4506
|
+
*
|
|
4507
|
+
* Fail closed on exactly the shapes whose evaluation escapes that analysis or
|
|
4508
|
+
* reads outside the snapshot:
|
|
4509
|
+
* - defaults (`{ a = expr }`): `expr` evaluates at destructure time, and its
|
|
4510
|
+
* reads are invisible to the body walks;
|
|
4511
|
+
* - computed keys (`{ [k]: v }`): same, for the key expression;
|
|
4512
|
+
* - a `current` binding at any depth: the same mutable-ref hazard the body
|
|
4513
|
+
* walk rejects for ObjectPattern declarations (a ref's identity is not a
|
|
4514
|
+
* complete witness for its contents);
|
|
4515
|
+
* - array patterns: destructuring runs the iterator protocol, which is
|
|
4516
|
+
* arbitrary user code on a non-Array prop;
|
|
4517
|
+
* - rest (`...rest`) with anything but a plain Identifier target.
|
|
4518
|
+
* Rest itself is admitted: object rest is an engine-level own-property copy of
|
|
4519
|
+
* the same snapshot, and spread-bearing call sites never receive a region
|
|
4520
|
+
* anyway (callSiteOk excludes them), so a getter-bearing props object cannot
|
|
4521
|
+
* reach a cached region.
|
|
4522
|
+
*/
|
|
4523
|
+
function isAutoMemoPropsParam(param) {
|
|
4524
|
+
if (param == null) return false;
|
|
4525
|
+
if (param.type === 'Identifier') return true;
|
|
4526
|
+
if (param.type !== 'ObjectPattern') return false;
|
|
4527
|
+
const admissible = (pattern) => {
|
|
4528
|
+
if (pattern == null || pattern.type !== 'ObjectPattern') return false;
|
|
4529
|
+
for (const p of pattern.properties || []) {
|
|
4530
|
+
if (p.type === 'RestElement') {
|
|
4531
|
+
if (p.argument?.type !== 'Identifier') return false;
|
|
4532
|
+
continue;
|
|
4533
|
+
}
|
|
4534
|
+
if (p.type !== 'Property' || p.computed) return false;
|
|
4535
|
+
const key = p.key?.name ?? p.key?.value;
|
|
4536
|
+
if (key === 'current') return false;
|
|
4537
|
+
const value = p.value;
|
|
4538
|
+
if (value?.type === 'Identifier') continue;
|
|
4539
|
+
if (!admissible(value)) return false;
|
|
4540
|
+
}
|
|
4541
|
+
return true;
|
|
4542
|
+
};
|
|
4543
|
+
return admissible(param);
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4324
4546
|
/**
|
|
4325
4547
|
* Bundling MOVES an argument's evaluation out of the arrow body and into the
|
|
4326
4548
|
* component body, so it now runs on mount and on every update instead of once
|
|
@@ -6953,6 +7175,7 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
6953
7175
|
eligible: false,
|
|
6954
7176
|
autoMemoSafe: false,
|
|
6955
7177
|
autoMemoCallsitesSafe: true,
|
|
7178
|
+
autoMemoLocalHazards: null,
|
|
6956
7179
|
autoMemoCaptures: [],
|
|
6957
7180
|
autoMemoComponentDeps: [],
|
|
6958
7181
|
autoMemoImportedComponents: [],
|
|
@@ -6985,16 +7208,26 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
6985
7208
|
if (compNode.body.render) stmts.push(compNode.body.render);
|
|
6986
7209
|
const root = b.block(stmts);
|
|
6987
7210
|
const free = collectFreeIdentifiers(root, locals);
|
|
6988
|
-
const { importedComponents: autoMemoImportedComponents
|
|
7211
|
+
const { importedComponents: autoMemoImportedComponents } = scanComponentBody(
|
|
6989
7212
|
root,
|
|
6990
7213
|
ctx.importedNames,
|
|
6991
7214
|
);
|
|
6992
|
-
//
|
|
6993
|
-
// short-circuits ahead of
|
|
7215
|
+
// The callee-side proof below asks this of the same root. Kept lazy because
|
|
7216
|
+
// the short-circuits ahead of its use skip it entirely for some components.
|
|
6994
7217
|
let importedMemberRead = null;
|
|
6995
7218
|
const readsImportedMember = () =>
|
|
6996
7219
|
(importedMemberRead ??= containsImportedMemberRead(root, ctx.importedNames));
|
|
6997
|
-
|
|
7220
|
+
// Call-site regions inside this body are gated per site: the site's own
|
|
7221
|
+
// props are walked directly (containsAutoMemoUnsafeStructure /
|
|
7222
|
+
// containsImportedMemberRead over the JSX node), and ref/imported-member
|
|
7223
|
+
// reads laundered through a body local are carried by this hazard set —
|
|
7224
|
+
// a site whose free identifiers touch a tainted local falls back. The
|
|
7225
|
+
// free-name loop below still vetoes the whole body for names no dep can
|
|
7226
|
+
// witness at all (boundary wrappers, namespaces, ambient module state).
|
|
7227
|
+
const autoMemoLocalHazards = ctx.autoMemo
|
|
7228
|
+
? collectAutoMemoLocalHazards(stmts, ctx.importedNames)
|
|
7229
|
+
: null;
|
|
7230
|
+
let autoMemoCallsitesSafe = true;
|
|
6998
7231
|
for (const name of free) {
|
|
6999
7232
|
if (
|
|
7000
7233
|
ctx._octaneBoundaryNames.has(name) ||
|
|
@@ -7008,13 +7241,14 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
7008
7241
|
break;
|
|
7009
7242
|
}
|
|
7010
7243
|
}
|
|
7011
|
-
// autoMemo
|
|
7012
|
-
//
|
|
7013
|
-
//
|
|
7014
|
-
//
|
|
7244
|
+
// autoMemo models a pure component as a function of one ordinary props
|
|
7245
|
+
// snapshot. A destructuring param is that same snapshot read once at
|
|
7246
|
+
// entry, admitted only while the pattern evaluates no expressions of its
|
|
7247
|
+
// own — see isAutoMemoPropsParam for the exact fail-closed shapes
|
|
7248
|
+
// (defaults, computed keys, `current`, array patterns).
|
|
7015
7249
|
const ordinaryPropsParam =
|
|
7016
7250
|
(compNode.params?.length ?? 0) <= 1 &&
|
|
7017
|
-
(compNode.params?.length !== 1 || compNode.params[0]
|
|
7251
|
+
(compNode.params?.length !== 1 || isAutoMemoPropsParam(compNode.params[0]));
|
|
7018
7252
|
let autoMemoSafe =
|
|
7019
7253
|
ordinaryPropsParam &&
|
|
7020
7254
|
!containsRenderCall(stmts, ctx) &&
|
|
@@ -7052,6 +7286,7 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
7052
7286
|
}
|
|
7053
7287
|
info.autoMemoSafe = autoMemoSafe;
|
|
7054
7288
|
info.autoMemoCallsitesSafe = autoMemoCallsitesSafe;
|
|
7289
|
+
info.autoMemoLocalHazards = autoMemoLocalHazards;
|
|
7055
7290
|
info.autoMemoCaptures = autoMemoSafe ? autoMemoCaptures.sort() : [];
|
|
7056
7291
|
info.autoMemoComponentDeps = autoMemoSafe ? autoMemoComponentDeps.sort() : [];
|
|
7057
7292
|
info.autoMemoImportedComponents = autoMemoSafe ? [...autoMemoImportedComponents].sort() : [];
|
|
@@ -10854,11 +11089,13 @@ function compileComponent(node, ctx, options) {
|
|
|
10854
11089
|
// locals.
|
|
10855
11090
|
const prevLocals = ctx.currentComponentLocals;
|
|
10856
11091
|
const prevAutoMemoCallsitesSafe = ctx.currentAutoMemoCallsitesSafe;
|
|
11092
|
+
const prevAutoMemoLocalHazards = ctx.currentAutoMemoLocalHazards;
|
|
10857
11093
|
const prevKnownStr = ctx.knownStringLocals;
|
|
10858
11094
|
const prevProfileComponentId = ctx.currentProfileComponentId;
|
|
10859
11095
|
const previousComponentOwner = ctx.currentComponentOwner;
|
|
10860
11096
|
ctx.currentComponentLocals = collectComponentLocals(node);
|
|
10861
11097
|
ctx.currentAutoMemoCallsitesSafe = ctx.componentInfo.get(name)?.autoMemoCallsitesSafe !== false;
|
|
11098
|
+
ctx.currentAutoMemoLocalHazards = ctx.componentInfo.get(name)?.autoMemoLocalHazards ?? null;
|
|
10862
11099
|
ctx.knownStringLocals = collectKnownStringLocals(node);
|
|
10863
11100
|
ctx.currentComponentOwner = owner;
|
|
10864
11101
|
if (ctx.profile) ctx.currentProfileComponentId = profileComponentId(ctx, name, node);
|
|
@@ -10876,6 +11113,7 @@ function compileComponent(node, ctx, options) {
|
|
|
10876
11113
|
} finally {
|
|
10877
11114
|
ctx.currentComponentLocals = prevLocals;
|
|
10878
11115
|
ctx.currentAutoMemoCallsitesSafe = prevAutoMemoCallsitesSafe;
|
|
11116
|
+
ctx.currentAutoMemoLocalHazards = prevAutoMemoLocalHazards;
|
|
10879
11117
|
ctx.knownStringLocals = prevKnownStr;
|
|
10880
11118
|
ctx.currentProfileComponentId = prevProfileComponentId;
|
|
10881
11119
|
ctx.currentComponentOwner = previousComponentOwner;
|
|
@@ -14243,11 +14481,13 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
14243
14481
|
const prevValueDirectiveLowering = ctx._valueDirectiveLowering;
|
|
14244
14482
|
const prevLocals = ctx.currentComponentLocals;
|
|
14245
14483
|
const prevAutoMemoCallsitesSafe = ctx.currentAutoMemoCallsitesSafe;
|
|
14484
|
+
const prevAutoMemoLocalHazards = ctx.currentAutoMemoLocalHazards;
|
|
14246
14485
|
const prevMapTemps = ctx.currentMapTemps;
|
|
14247
14486
|
const mapTemps = [];
|
|
14248
14487
|
ctx._valueDirectiveLowering = lowerBodyValueDirective;
|
|
14249
14488
|
ctx.currentComponentLocals = collectComponentLocals(node);
|
|
14250
14489
|
ctx.currentAutoMemoCallsitesSafe = ctx.componentInfo.get(name)?.autoMemoCallsitesSafe !== false;
|
|
14490
|
+
ctx.currentAutoMemoLocalHazards = ctx.componentInfo.get(name)?.autoMemoLocalHazards ?? null;
|
|
14251
14491
|
ctx.currentMapTemps = mapTemps;
|
|
14252
14492
|
let newStatements;
|
|
14253
14493
|
try {
|
|
@@ -14295,6 +14535,7 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
14295
14535
|
ctx._valueDirectiveLowering = prevValueDirectiveLowering;
|
|
14296
14536
|
ctx.currentComponentLocals = prevLocals;
|
|
14297
14537
|
ctx.currentAutoMemoCallsitesSafe = prevAutoMemoCallsitesSafe;
|
|
14538
|
+
ctx.currentAutoMemoLocalHazards = prevAutoMemoLocalHazards;
|
|
14298
14539
|
ctx.currentMapTemps = prevMapTemps;
|
|
14299
14540
|
}
|
|
14300
14541
|
if (
|
|
@@ -21898,7 +22139,13 @@ function makeCompCall(
|
|
|
21898
22139
|
}
|
|
21899
22140
|
for (const name of free) {
|
|
21900
22141
|
if (name === compName) continue;
|
|
21901
|
-
if (
|
|
22142
|
+
if (
|
|
22143
|
+
ctx.importNamespaceNames.has(name) ||
|
|
22144
|
+
// A local laundering a ref or live-import member read (see
|
|
22145
|
+
// collectAutoMemoLocalHazards) — its per-render value is not a
|
|
22146
|
+
// complete witness, so the site keeps ordinary entry semantics.
|
|
22147
|
+
ctx.currentAutoMemoLocalHazards?.has(name) === true
|
|
22148
|
+
) {
|
|
21902
22149
|
depsSafe = false;
|
|
21903
22150
|
} else if (ctx.currentComponentLocals.has(name) || ctx.importedNames.has(name)) {
|
|
21904
22151
|
if (!callsiteDeps.coveredRoots.has(name)) deps.add(name);
|
|
@@ -22570,7 +22817,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
22570
22817
|
// survivor shortcut has no compiler-cache epoch cell to consult).
|
|
22571
22818
|
if (itemMemoContextAware && autoMemoDeps === null) itemMemo = false;
|
|
22572
22819
|
const hostPure = !hasParentClosure && !hasHook && !hasNestedComp && !hasRenderCall;
|
|
22573
|
-
const
|
|
22820
|
+
const structuredHostDepEligible =
|
|
22574
22821
|
ctx.autoMemo === true &&
|
|
22575
22822
|
hasNestedComp &&
|
|
22576
22823
|
hasParentClosure &&
|
|
@@ -22578,14 +22825,24 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
22578
22825
|
!hasRenderCall &&
|
|
22579
22826
|
node.nativeArrayMap === undefined &&
|
|
22580
22827
|
autoMemoDeps !== null &&
|
|
22581
|
-
autoMemoDeps.every((name) => seenDeps.has(name)) &&
|
|
22828
|
+
autoMemoDeps.every((name) => seenDeps.has(name) || ctx.importedNames.has(name)) &&
|
|
22582
22829
|
!containsAutoMemoContextRead(bodyAst, ctx) &&
|
|
22583
22830
|
hasOnlyHostConditionalItemBodies(subStmts);
|
|
22831
|
+
if (structuredHostDepEligible) {
|
|
22832
|
+
// The whole-list cache already witnesses imported projections. A row
|
|
22833
|
+
// survivor now makes the same decision independently, so its existing
|
|
22834
|
+
// dependency tuple must witness those live bindings as well.
|
|
22835
|
+
for (const name of autoMemoDeps) {
|
|
22836
|
+
if (seenDeps.has(name)) continue;
|
|
22837
|
+
seenDeps.add(name);
|
|
22838
|
+
depNames.push(name);
|
|
22839
|
+
}
|
|
22840
|
+
}
|
|
22584
22841
|
const hostDepEligible =
|
|
22585
22842
|
!hostPure &&
|
|
22586
22843
|
!hasHook &&
|
|
22587
22844
|
hasParentClosure &&
|
|
22588
|
-
(!hasNestedComp ||
|
|
22845
|
+
(!hasNestedComp || structuredHostDepEligible) &&
|
|
22589
22846
|
!hasRenderCall;
|
|
22590
22847
|
if (itemMemo && itemMemoWitnesses.length > 0) {
|
|
22591
22848
|
pure = hostPure;
|
|
@@ -22595,7 +22852,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
22595
22852
|
pure = hostPure || (itemMemo && depNames.length === 0);
|
|
22596
22853
|
depEligible = !pure && !hasHook && (hostDepEligible || (itemMemo && depNames.length > 0));
|
|
22597
22854
|
}
|
|
22598
|
-
requiresScope = depEligible &&
|
|
22855
|
+
requiresScope = depEligible && structuredHostDepEligible;
|
|
22599
22856
|
depNames.sort();
|
|
22600
22857
|
}
|
|
22601
22858
|
|
package/dist/runtime.js
CHANGED
|
@@ -2155,6 +2155,7 @@ function renderBlockInner(block) {
|
|
|
2155
2155
|
if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__ && profileFrame !== null)
|
|
2156
2156
|
__profileEndRender(profileFrame, profileDidThrow, profileThrown);
|
|
2157
2157
|
if (!renderCompleted) {
|
|
2158
|
+
if (block.forSlot !== null) block.forSlot.cachedDeps = null;
|
|
2158
2159
|
effectEventTarget.length = effectEventCheckpoint;
|
|
2159
2160
|
effectEventActionTarget.length = effectEventActionCheckpoint;
|
|
2160
2161
|
}
|