@llui/vite-plugin 0.0.42 → 0.2.0
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/README.md +2 -2
- package/dist/collect-deps.d.ts +16 -2
- package/dist/collect-deps.d.ts.map +1 -1
- package/dist/collect-deps.js +25 -13
- package/dist/collect-deps.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +214 -101
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
package/dist/transform.js
CHANGED
|
@@ -227,7 +227,9 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
227
227
|
// function is generated per-component, so bit assignments in other files
|
|
228
228
|
// won't match. Files without component() get FULL_MASK on all bindings.
|
|
229
229
|
const fileHasComponent = hasComponentDef(sourceFile, lluiImport);
|
|
230
|
-
const
|
|
230
|
+
const { lo: fieldBits, hi: fieldBitsHi } = fileHasComponent
|
|
231
|
+
? collectDeps(source)
|
|
232
|
+
: { lo: new Map(), hi: new Map() };
|
|
231
233
|
if (verbose && fileHasComponent) {
|
|
232
234
|
const pairs = [...fieldBits.entries()]
|
|
233
235
|
.map(([path, bit]) => `${path}=${bit === -1 ? 'FULL' : bit}`)
|
|
@@ -301,7 +303,7 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
301
303
|
}
|
|
302
304
|
// Pass 1: Transform element helper calls to elSplit or elTemplate
|
|
303
305
|
if (ts.isCallExpression(node)) {
|
|
304
|
-
const transformed = tryTransformElementCall(node, importedHelpers, fieldBits, compiledHelpers, bailedHelpers, f);
|
|
306
|
+
const transformed = tryTransformElementCall(node, importedHelpers, fieldBits, compiledHelpers, bailedHelpers, f, fieldBitsHi);
|
|
305
307
|
if (transformed) {
|
|
306
308
|
if (ts.isIdentifier(transformed.expression)) {
|
|
307
309
|
if (transformed.expression.text === 'elTemplate')
|
|
@@ -332,7 +334,7 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
332
334
|
}
|
|
333
335
|
// Pass 2: Inject __dirty, __update, and __msgSchema into component() calls
|
|
334
336
|
if (ts.isCallExpression(node) && isComponentCall(node, lluiImport)) {
|
|
335
|
-
let result = tryInjectDirty(node, fieldBits, f);
|
|
337
|
+
let result = tryInjectDirty(node, fieldBits, f, fieldBitsHi);
|
|
336
338
|
if (result)
|
|
337
339
|
usesApplyBinding = true;
|
|
338
340
|
// Extract schema data once — used both for devMode injections and the
|
|
@@ -767,7 +769,7 @@ function emitStaticProp(staticProps, f, kind, resolvedKey, value) {
|
|
|
767
769
|
}
|
|
768
770
|
}
|
|
769
771
|
// ── Pass 1: Element → elSplit ────────────────────────────────────
|
|
770
|
-
function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f) {
|
|
772
|
+
function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f, fieldBitsHi = new Map()) {
|
|
771
773
|
if (!ts.isIdentifier(node.expression))
|
|
772
774
|
return null;
|
|
773
775
|
const localName = node.expression.text;
|
|
@@ -890,7 +892,7 @@ function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f)
|
|
|
890
892
|
{
|
|
891
893
|
const kind = classifyKind(key);
|
|
892
894
|
const resolvedKey = resolveKey(key, kind);
|
|
893
|
-
const { mask, readsState } = computeAccessorMask(classified.accessor, fieldBits);
|
|
895
|
+
const { mask, maskHi, readsState } = computeAccessorMask(classified.accessor, fieldBits, undefined, fieldBitsHi);
|
|
894
896
|
// Zero-mask constant folding only applies to inline arrows whose body
|
|
895
897
|
// we can safely call at compile time. For identifier-bound forms
|
|
896
898
|
// (`accessor !== value`) we skip the fold — calling the identifier's
|
|
@@ -899,16 +901,25 @@ function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f)
|
|
|
899
901
|
if (classified.kind === 'arrow' &&
|
|
900
902
|
classified.accessor === value &&
|
|
901
903
|
mask === 0 &&
|
|
904
|
+
maskHi === 0 &&
|
|
902
905
|
!readsState) {
|
|
903
906
|
emitStaticProp(staticProps, f, kind, resolvedKey, f.createCallExpression(classified.accessor, undefined, []));
|
|
904
907
|
continue;
|
|
905
908
|
}
|
|
906
|
-
|
|
907
|
-
|
|
909
|
+
const effectiveMask = mask === 0 && maskHi === 0 && readsState ? 0xffffffff | 0 : mask;
|
|
910
|
+
// Emit a 5-tuple only when the accessor reads a high-word
|
|
911
|
+
// prefix (positions 31..61). For the common ≤31-prefix case
|
|
912
|
+
// the emit stays byte-identical to the pre-multi-word baseline,
|
|
913
|
+
// and stale runtime bundles ignore the 5th slot.
|
|
914
|
+
const tupleEls = [
|
|
915
|
+
createMaskLiteral(f, effectiveMask),
|
|
908
916
|
f.createStringLiteral(kind),
|
|
909
917
|
f.createStringLiteral(resolvedKey),
|
|
910
918
|
classified.valueForBinding,
|
|
911
|
-
]
|
|
919
|
+
];
|
|
920
|
+
if (maskHi !== 0)
|
|
921
|
+
tupleEls.push(createMaskLiteral(f, maskHi));
|
|
922
|
+
bindings.push(f.createArrayLiteralExpression(tupleEls));
|
|
912
923
|
}
|
|
913
924
|
}
|
|
914
925
|
}
|
|
@@ -924,7 +935,7 @@ function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f)
|
|
|
924
935
|
compiled.add(localName);
|
|
925
936
|
// Subtree collapse: if children contain nested element helpers,
|
|
926
937
|
// collapse the entire tree into a single elTemplate() call
|
|
927
|
-
const analyzed = analyzeSubtree(node, helpers, fieldBits, []);
|
|
938
|
+
const analyzed = analyzeSubtree(node, helpers, fieldBits, [], fieldBitsHi);
|
|
928
939
|
if (analyzed && hasNestedElements(analyzed)) {
|
|
929
940
|
// Mark all descendant helpers as compiled for import cleanup
|
|
930
941
|
collectUsedHelpers(analyzed, compiled);
|
|
@@ -1072,8 +1083,8 @@ function tryInjectStructuralMask(node, viewHelperNames, viewHelperAliases, field
|
|
|
1072
1083
|
...node.arguments.slice(1),
|
|
1073
1084
|
]);
|
|
1074
1085
|
}
|
|
1075
|
-
function tryInjectDirty(node, fieldBits, f) {
|
|
1076
|
-
if (fieldBits.size === 0)
|
|
1086
|
+
function tryInjectDirty(node, fieldBits, f, fieldBitsHi = new Map()) {
|
|
1087
|
+
if (fieldBits.size === 0 && fieldBitsHi.size === 0)
|
|
1077
1088
|
return null;
|
|
1078
1089
|
const configArg = node.arguments[0];
|
|
1079
1090
|
if (!configArg || !ts.isObjectLiteralExpression(configArg))
|
|
@@ -1086,36 +1097,20 @@ function tryInjectDirty(node, fieldBits, f) {
|
|
|
1086
1097
|
return null;
|
|
1087
1098
|
}
|
|
1088
1099
|
}
|
|
1089
|
-
//
|
|
1090
|
-
//
|
|
1091
|
-
//
|
|
1092
|
-
//
|
|
1100
|
+
// Top-level field → aggregated bit mask. Sub-paths under one field
|
|
1101
|
+
// (`route.page`, `route.data`) collapse into a single entry so
|
|
1102
|
+
// `tryBuildHandlers` and `__maskLegend` can reason per-field. Positions
|
|
1103
|
+
// 0..30 live here; 31..61 in the parallel high-word map below.
|
|
1093
1104
|
const topLevelBits = new Map();
|
|
1094
1105
|
for (const [path, bit] of fieldBits) {
|
|
1095
1106
|
const topField = path.split('.')[0];
|
|
1096
1107
|
topLevelBits.set(topField, (topLevelBits.get(topField) ?? 0) | bit);
|
|
1097
1108
|
}
|
|
1098
|
-
const
|
|
1099
|
-
for (const [
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
let dirtyBody = comparisons[0];
|
|
1105
|
-
for (let i = 1; i < comparisons.length; i++) {
|
|
1106
|
-
dirtyBody = f.createBinaryExpression(dirtyBody, ts.SyntaxKind.BarToken, comparisons[i]);
|
|
1107
|
-
}
|
|
1108
|
-
// Fallback: if no tracked bit fired but the state reference changed, some
|
|
1109
|
-
// untracked field must have changed — return FULL_MASK so bindings whose
|
|
1110
|
-
// accessors came from external modules (spread parts) still fire.
|
|
1111
|
-
// tracked || (Object.is(o, n) ? 0 : FULL_MASK)
|
|
1112
|
-
const fallback = f.createParenthesizedExpression(f.createConditionalExpression(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('Object'), 'is'), undefined, [f.createIdentifier('o'), f.createIdentifier('n')]), f.createToken(ts.SyntaxKind.QuestionToken), f.createNumericLiteral(0), f.createToken(ts.SyntaxKind.ColonToken), createMaskLiteral(f, -1)));
|
|
1113
|
-
dirtyBody = f.createBinaryExpression(f.createParenthesizedExpression(dirtyBody), ts.SyntaxKind.BarBarToken, fallback);
|
|
1114
|
-
const dirtyFn = f.createArrowFunction(undefined, undefined, [
|
|
1115
|
-
f.createParameterDeclaration(undefined, undefined, 'o'),
|
|
1116
|
-
f.createParameterDeclaration(undefined, undefined, 'n'),
|
|
1117
|
-
], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), dirtyBody);
|
|
1118
|
-
const dirtyProp = f.createPropertyAssignment('__dirty', dirtyFn);
|
|
1109
|
+
const topLevelBitsHi = new Map();
|
|
1110
|
+
for (const [path, bit] of fieldBitsHi) {
|
|
1111
|
+
const topField = path.split('.')[0];
|
|
1112
|
+
topLevelBitsHi.set(topField, (topLevelBitsHi.get(topField) ?? 0) | bit);
|
|
1113
|
+
}
|
|
1119
1114
|
// __maskLegend: maps each top-level state field to the bit(s) that fire when
|
|
1120
1115
|
// it changes. Lets introspection tools decode runtime dirty masks to field names.
|
|
1121
1116
|
const legendProps = [];
|
|
@@ -1128,25 +1123,59 @@ function tryInjectDirty(node, fieldBits, f) {
|
|
|
1128
1123
|
const legendProp = f.createPropertyAssignment('__maskLegend', f.createObjectLiteralExpression(legendProps, false));
|
|
1129
1124
|
// Structural mask — used by both __update and __handlers
|
|
1130
1125
|
const structuralMask = computeStructuralMask(configArg, fieldBits);
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1126
|
+
const updateBody = buildUpdateBody(f, structuralMask);
|
|
1127
|
+
// `dHi` is the high-word dirty mask, appended as the trailing
|
|
1128
|
+
// positional arg so stale 5-param compiled bundles continue to gate
|
|
1129
|
+
// correctly: the runtime calls `__update(s, d, b, bl, p, dHi)`,
|
|
1130
|
+
// old bundles' 5-param arrow ignores the extra arg (for ≤31-prefix
|
|
1131
|
+
// components dHi is always 0 anyway). New bundles use it for
|
|
1132
|
+
// precise two-word Phase 1 gating.
|
|
1133
1133
|
const updateFn = f.createArrowFunction(undefined, undefined, [
|
|
1134
1134
|
f.createParameterDeclaration(undefined, undefined, 's'),
|
|
1135
1135
|
f.createParameterDeclaration(undefined, undefined, 'd'),
|
|
1136
1136
|
f.createParameterDeclaration(undefined, undefined, 'b'),
|
|
1137
1137
|
f.createParameterDeclaration(undefined, undefined, 'bl'),
|
|
1138
1138
|
f.createParameterDeclaration(undefined, undefined, 'p'),
|
|
1139
|
+
f.createParameterDeclaration(undefined, undefined, 'dHi', undefined, undefined, f.createNumericLiteral(0)),
|
|
1139
1140
|
], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), updateBody);
|
|
1140
1141
|
const updateProp = f.createPropertyAssignment('__update', updateFn);
|
|
1141
1142
|
// __handlers: per-message-type specialized update functions.
|
|
1142
1143
|
// Analyzes the update() switch/case and generates direct handlers
|
|
1143
1144
|
// that bypass the generic Phase 1/2 pipeline for single-message updates.
|
|
1144
|
-
const handlersProp = tryBuildHandlers(configArg, topLevelBits, structuralMask, f);
|
|
1145
|
-
//
|
|
1146
|
-
// mask
|
|
1147
|
-
|
|
1145
|
+
const handlersProp = tryBuildHandlers(configArg, topLevelBits, topLevelBitsHi, structuralMask, f);
|
|
1146
|
+
// Both `__update` and `__handlers` carry two-word gates: `__update`'s
|
|
1147
|
+
// Phase 1 block loop uses `(mask & d) | (maskHi & dHi)`, and
|
|
1148
|
+
// `__handlers` passes `caseDirtyHi` to `_handleMsg` which gates blocks
|
|
1149
|
+
// against both words. `dHi` defaults to 0 so any stale 5-arg call site
|
|
1150
|
+
// still works. `__dirty` is no longer emitted — `__prefixes` (below)
|
|
1151
|
+
// is strictly more precise, and the runtime throws on hand-authored
|
|
1152
|
+
// `__dirty`. `__maskLegend` survives because the agent layer uses it
|
|
1153
|
+
// to decode runtime dirty masks back to top-level field names.
|
|
1154
|
+
const extraProps = [legendProp, updateProp];
|
|
1148
1155
|
if (handlersProp)
|
|
1149
1156
|
extraProps.push(handlersProp);
|
|
1157
|
+
// __prefixes: opt-in path-keyed reactivity (see
|
|
1158
|
+
// docs/proposals/unified-composition-model.md). One closure per
|
|
1159
|
+
// distinct path that an accessor reads, hoisted into a stable array;
|
|
1160
|
+
// the array position IS the bit position used by the path's bindings.
|
|
1161
|
+
// The runtime prefers __prefixes when present and computes the
|
|
1162
|
+
// combinedDirty mask by reference-comparing `prefix(prev)` vs
|
|
1163
|
+
// `prefix(next)` for each entry — strictly more precise than the
|
|
1164
|
+
// top-level-conflated __dirty (which always co-fires bindings sharing
|
|
1165
|
+
// a top-level field even when only one sub-path actually mutated).
|
|
1166
|
+
//
|
|
1167
|
+
// Emit `__prefixes` whenever any reactive paths are present. For
|
|
1168
|
+
// components with ≤31 paths, the runtime's
|
|
1169
|
+
// `computeDirtyFromPrefixes` returns a single `number`; for
|
|
1170
|
+
// 32..61-path components it returns a `[lo, hi]` tuple that the
|
|
1171
|
+
// runtime fans out into `combinedDirty` + `combinedDirtyHi`. The
|
|
1172
|
+
// binding-level mask gating is still single-word at the compiler
|
|
1173
|
+
// emit layer today, so high-position bindings still re-evaluate
|
|
1174
|
+
// every cycle — but the dirty computation itself is now precise,
|
|
1175
|
+
// which lets memo()'d aggregates short-circuit correctly.
|
|
1176
|
+
const prefixesProp = buildPrefixesProp(fieldBits, fieldBitsHi, f);
|
|
1177
|
+
if (prefixesProp)
|
|
1178
|
+
extraProps.push(prefixesProp);
|
|
1150
1179
|
const newConfig = f.createObjectLiteralExpression([...configArg.properties, ...extraProps], true);
|
|
1151
1180
|
return f.createCallExpression(node.expression, node.typeArguments, [
|
|
1152
1181
|
newConfig,
|
|
@@ -1164,8 +1193,8 @@ function tryInjectDirty(node, fieldBits, f) {
|
|
|
1164
1193
|
* Conservative: only generates handlers for cases where the field
|
|
1165
1194
|
* modifications are statically determinable. Complex cases are skipped.
|
|
1166
1195
|
*/
|
|
1167
|
-
function tryBuildHandlers(configArg, topLevelBits, structuralMask, f) {
|
|
1168
|
-
if (topLevelBits.size === 0)
|
|
1196
|
+
function tryBuildHandlers(configArg, topLevelBits, topLevelBitsHi, structuralMask, f) {
|
|
1197
|
+
if (topLevelBits.size === 0 && topLevelBitsHi.size === 0)
|
|
1169
1198
|
return null;
|
|
1170
1199
|
// Find the update function in the component config
|
|
1171
1200
|
let updateFn = null;
|
|
@@ -1241,7 +1270,7 @@ function tryBuildHandlers(configArg, topLevelBits, structuralMask, f) {
|
|
|
1241
1270
|
let bailOut = false;
|
|
1242
1271
|
for (const returnExpr of returnExprs) {
|
|
1243
1272
|
const stateExpr = returnExpr.elements[0];
|
|
1244
|
-
const fields = analyzeModifiedFields(stateExpr, stateName, topLevelBits);
|
|
1273
|
+
const fields = analyzeModifiedFields(stateExpr, stateName, topLevelBits, topLevelBitsHi);
|
|
1245
1274
|
if (!fields) {
|
|
1246
1275
|
bailOut = true;
|
|
1247
1276
|
break;
|
|
@@ -1252,14 +1281,29 @@ function tryBuildHandlers(configArg, topLevelBits, structuralMask, f) {
|
|
|
1252
1281
|
if (bailOut)
|
|
1253
1282
|
continue; // at least one return path was too complex
|
|
1254
1283
|
const modifiedFields = Array.from(allModified);
|
|
1255
|
-
// Compute the dirty mask for this case
|
|
1284
|
+
// Compute the dirty mask for this case across both words. Fields
|
|
1285
|
+
// tracked in `topLevelBitsHi` contribute to `caseDirtyHi`; fields
|
|
1286
|
+
// tracked nowhere (`undefined` lookup in both) fall back to
|
|
1287
|
+
// FULL_MASK in the low word — same conservative behavior as
|
|
1288
|
+
// before, just preserved per-word now.
|
|
1256
1289
|
let caseDirty = 0;
|
|
1290
|
+
let caseDirtyHi = 0;
|
|
1257
1291
|
for (const field of modifiedFields) {
|
|
1258
|
-
|
|
1292
|
+
const lo = topLevelBits.get(field);
|
|
1293
|
+
const hi = topLevelBitsHi.get(field);
|
|
1294
|
+
if (lo === undefined && hi === undefined) {
|
|
1295
|
+
caseDirty |= 0xffffffff | 0;
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
if (lo !== undefined)
|
|
1299
|
+
caseDirty |= lo;
|
|
1300
|
+
if (hi !== undefined)
|
|
1301
|
+
caseDirtyHi |= hi;
|
|
1302
|
+
}
|
|
1259
1303
|
}
|
|
1260
1304
|
// Detect array operation pattern for structural block optimization
|
|
1261
1305
|
const arrayOp = detectArrayOp(clause, stateName, modifiedFields, structuralMask, caseDirty);
|
|
1262
|
-
const handler = buildCaseHandler(f, caseDirty, arrayOp);
|
|
1306
|
+
const handler = buildCaseHandler(f, caseDirty, caseDirtyHi, arrayOp);
|
|
1263
1307
|
handlers.push(f.createPropertyAssignment(f.createStringLiteral(msgType), handler));
|
|
1264
1308
|
}
|
|
1265
1309
|
if (handlers.length === 0)
|
|
@@ -1445,7 +1489,12 @@ function hasSliceAssignment(clause, stateName, varName) {
|
|
|
1445
1489
|
* Analyze which top-level state fields are modified in a return expression.
|
|
1446
1490
|
* Returns the set of field names, or null if too complex to determine.
|
|
1447
1491
|
*/
|
|
1448
|
-
function analyzeModifiedFields(stateExpr, stateName, topLevelBits) {
|
|
1492
|
+
function analyzeModifiedFields(stateExpr, stateName, topLevelBits, topLevelBitsHi = new Map()) {
|
|
1493
|
+
// Recognize fields tracked in EITHER the low-word or high-word map.
|
|
1494
|
+
// 32..61-prefix components have their overflow paths in
|
|
1495
|
+
// `topLevelBitsHi`; the case handler's `caseDirty` / `caseDirtyHi`
|
|
1496
|
+
// logic depends on us recognizing those fields here.
|
|
1497
|
+
const isTracked = (name) => topLevelBits.has(name) || topLevelBitsHi.has(name);
|
|
1449
1498
|
// Pattern: { ...state, field1: ..., field2: ... } or { field1: ..., field2: ... }
|
|
1450
1499
|
if (ts.isObjectLiteralExpression(stateExpr)) {
|
|
1451
1500
|
const modified = [];
|
|
@@ -1464,14 +1513,14 @@ function analyzeModifiedFields(stateExpr, stateName, topLevelBits) {
|
|
|
1464
1513
|
}
|
|
1465
1514
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
1466
1515
|
const fieldName = prop.name.text;
|
|
1467
|
-
if (
|
|
1516
|
+
if (isTracked(fieldName)) {
|
|
1468
1517
|
modified.push(fieldName);
|
|
1469
1518
|
}
|
|
1470
1519
|
}
|
|
1471
1520
|
// Handle shorthand: { ...state, rows } where rows is a local variable
|
|
1472
1521
|
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
1473
1522
|
const fieldName = prop.name.text;
|
|
1474
|
-
if (
|
|
1523
|
+
if (isTracked(fieldName)) {
|
|
1475
1524
|
modified.push(fieldName);
|
|
1476
1525
|
}
|
|
1477
1526
|
}
|
|
@@ -1504,7 +1553,7 @@ function analyzeModifiedFields(stateExpr, stateName, topLevelBits) {
|
|
|
1504
1553
|
* Build a handler that delegates to __handleMsg(inst, msg, dirty, method).
|
|
1505
1554
|
* method: 0=reconcile, 1=reconcileItems, 2=reconcileClear, 3=reconcileRemove, -1=skip blocks
|
|
1506
1555
|
*/
|
|
1507
|
-
function buildCaseHandler(f, caseDirty, arrayOp) {
|
|
1556
|
+
function buildCaseHandler(f, caseDirty, caseDirtyHi, arrayOp) {
|
|
1508
1557
|
const method = typeof arrayOp === 'object' && arrayOp.type === 'strided'
|
|
1509
1558
|
? 10 + arrayOp.stride // reconcileChanged with stride
|
|
1510
1559
|
: arrayOp === 'none'
|
|
@@ -1516,18 +1565,24 @@ function buildCaseHandler(f, caseDirty, arrayOp) {
|
|
|
1516
1565
|
: arrayOp === 'remove'
|
|
1517
1566
|
? 3
|
|
1518
1567
|
: 0; // general
|
|
1519
|
-
// (inst, msg) => __handleMsg(inst, msg, dirty, method)
|
|
1520
|
-
|
|
1521
|
-
f.createParameterDeclaration(undefined, undefined, 'inst'),
|
|
1522
|
-
f.createParameterDeclaration(undefined, undefined, 'msg'),
|
|
1523
|
-
], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createCallExpression(f.createIdentifier('__handleMsg'), undefined, [
|
|
1568
|
+
// (inst, msg) => __handleMsg(inst, msg, dirty, method, [dirtyHi])
|
|
1569
|
+
const args = [
|
|
1524
1570
|
f.createIdentifier('inst'),
|
|
1525
1571
|
f.createIdentifier('msg'),
|
|
1526
1572
|
createMaskLiteral(f, caseDirty),
|
|
1527
1573
|
method >= 0
|
|
1528
1574
|
? f.createNumericLiteral(method)
|
|
1529
1575
|
: f.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, f.createNumericLiteral(1)),
|
|
1530
|
-
]
|
|
1576
|
+
];
|
|
1577
|
+
// Emit the 5th positional arg only when the case touches a high-word
|
|
1578
|
+
// field. Stale runtime bundles' _handleMsg signatures ignored that
|
|
1579
|
+
// slot anyway; new ones (defaulted to 0) make it explicit when needed.
|
|
1580
|
+
if (caseDirtyHi !== 0)
|
|
1581
|
+
args.push(createMaskLiteral(f, caseDirtyHi));
|
|
1582
|
+
return f.createArrowFunction(undefined, undefined, [
|
|
1583
|
+
f.createParameterDeclaration(undefined, undefined, 'inst'),
|
|
1584
|
+
f.createParameterDeclaration(undefined, undefined, 'msg'),
|
|
1585
|
+
], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createCallExpression(f.createIdentifier('__handleMsg'), undefined, args));
|
|
1531
1586
|
}
|
|
1532
1587
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1533
1588
|
function _deadCode_legacyCaseHandler(f, caseDirty, arrayOp) {
|
|
@@ -2010,17 +2065,6 @@ function computeStructuralMask(configArg, fieldBits) {
|
|
|
2010
2065
|
walk(viewProp.initializer);
|
|
2011
2066
|
return foundStructural ? mask || 0xffffffff | 0 : 0;
|
|
2012
2067
|
}
|
|
2013
|
-
/**
|
|
2014
|
-
* Compute the OR of all component-level binding masks from text() calls
|
|
2015
|
-
* and element bindings in the view. Returns 0 if no component-level bindings.
|
|
2016
|
-
*/
|
|
2017
|
-
function computePhase2Mask(_configArg, _fieldBits) {
|
|
2018
|
-
// For now, return FULL_MASK — a future pass can analyze all binding sites
|
|
2019
|
-
// in the view to compute the precise aggregate. The key optimization is
|
|
2020
|
-
// already in Phase 1 gating: when structuralMask doesn't intersect dirty,
|
|
2021
|
-
// the entire reconciliation is skipped.
|
|
2022
|
-
return 0xffffffff | 0;
|
|
2023
|
-
}
|
|
2024
2068
|
/**
|
|
2025
2069
|
* Build the __update function body:
|
|
2026
2070
|
* {
|
|
@@ -2053,16 +2097,22 @@ function computePhase2Mask(_configArg, _fieldBits) {
|
|
|
2053
2097
|
* }
|
|
2054
2098
|
* }
|
|
2055
2099
|
*/
|
|
2056
|
-
function buildUpdateBody(f, structuralMask
|
|
2100
|
+
function buildUpdateBody(f, structuralMask) {
|
|
2057
2101
|
const stmts = [];
|
|
2058
2102
|
// Phase 1: structural block reconciliation, gated by aggregate mask
|
|
2059
2103
|
if (structuralMask !== 0) {
|
|
2060
2104
|
const phase1Stmts = [];
|
|
2061
2105
|
// for (let i = 0; i < bl.length; i++) {
|
|
2062
2106
|
// const bk = bl[i];
|
|
2063
|
-
// if (!bk || (bk.mask & d)
|
|
2064
|
-
// bk.reconcile(s, d)
|
|
2107
|
+
// if (!bk || !((bk.mask & d) | (bk.maskHi & dHi))) continue;
|
|
2108
|
+
// bk.reconcile(s, d, dHi)
|
|
2065
2109
|
// }
|
|
2110
|
+
// Two-word gate matches the runtime's `genericUpdate`: bits 0..30
|
|
2111
|
+
// in `d`, bits 31..61 in `dHi`. For ≤31-prefix components both
|
|
2112
|
+
// `bk.maskHi` and `dHi` are 0, so V8's inline cache collapses the
|
|
2113
|
+
// OR back to the single-word check. >31-prefix components use the
|
|
2114
|
+
// high word for precise gating.
|
|
2115
|
+
//
|
|
2066
2116
|
// Re-read bl.length each iteration and null-check bk — a branch's
|
|
2067
2117
|
// reconcile may dispose the old scope, whose disposers splice child
|
|
2068
2118
|
// structural blocks out of this shared array mid-iteration.
|
|
@@ -2070,8 +2120,8 @@ function buildUpdateBody(f, structuralMask, _phase2Mask) {
|
|
|
2070
2120
|
f.createVariableStatement(undefined, f.createVariableDeclarationList([
|
|
2071
2121
|
f.createVariableDeclaration('bk', undefined, undefined, f.createElementAccessExpression(f.createIdentifier('bl'), f.createIdentifier('i'))),
|
|
2072
2122
|
], ts.NodeFlags.Const)),
|
|
2073
|
-
f.createIfStatement(f.createBinaryExpression(f.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, f.createIdentifier('bk')), ts.SyntaxKind.BarBarToken, f.createBinaryExpression(f.createParenthesizedExpression(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'mask'), ts.SyntaxKind.AmpersandToken, f.createIdentifier('d'))), ts.SyntaxKind.
|
|
2074
|
-
f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'reconcile'), undefined, [f.createIdentifier('s'), f.createIdentifier('d')])),
|
|
2123
|
+
f.createIfStatement(f.createBinaryExpression(f.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, f.createIdentifier('bk')), ts.SyntaxKind.BarBarToken, f.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, f.createParenthesizedExpression(f.createBinaryExpression(f.createParenthesizedExpression(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'mask'), ts.SyntaxKind.AmpersandToken, f.createIdentifier('d'))), ts.SyntaxKind.BarToken, f.createParenthesizedExpression(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'maskHi'), ts.SyntaxKind.AmpersandToken, f.createIdentifier('dHi'))))))), f.createContinueStatement()),
|
|
2124
|
+
f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'reconcile'), undefined, [f.createIdentifier('s'), f.createIdentifier('d'), f.createIdentifier('dHi')])),
|
|
2075
2125
|
], true));
|
|
2076
2126
|
phase1Stmts.push(blockLoop);
|
|
2077
2127
|
// Compaction: if (b.length > p || (p > 0 && b[0].dead)) { ... }
|
|
@@ -2097,15 +2147,50 @@ function buildUpdateBody(f, structuralMask, _phase2Mask) {
|
|
|
2097
2147
|
stmts.push(...phase1Stmts);
|
|
2098
2148
|
}
|
|
2099
2149
|
}
|
|
2100
|
-
// Phase 2: delegate to shared runtime — __runPhase2(s, d, b, p)
|
|
2150
|
+
// Phase 2: delegate to shared runtime — __runPhase2(s, d, dHi, b, p)
|
|
2101
2151
|
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createIdentifier('__runPhase2'), undefined, [
|
|
2102
2152
|
f.createIdentifier('s'),
|
|
2103
2153
|
f.createIdentifier('d'),
|
|
2154
|
+
f.createIdentifier('dHi'),
|
|
2104
2155
|
f.createIdentifier('b'),
|
|
2105
2156
|
f.createIdentifier('p'),
|
|
2106
2157
|
])));
|
|
2107
2158
|
return f.createBlock(stmts, true);
|
|
2108
2159
|
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Build the `__prefixes` property assignment from path → bit maps.
|
|
2162
|
+
*
|
|
2163
|
+
* Emits one arrow `(s) => s.<path>` per distinct path. Array index =
|
|
2164
|
+
* the path's bit position: positions 0..30 come from `fieldBits` (low
|
|
2165
|
+
* word), positions 31..61 from `fieldBitsHi` (high word). The runtime
|
|
2166
|
+
* walks this array and reference-compares `prefix(prev)` vs
|
|
2167
|
+
* `prefix(next)` per entry, fanning bits into a `(lo, hi)` pair when
|
|
2168
|
+
* the array length exceeds 31.
|
|
2169
|
+
*
|
|
2170
|
+
* Returns null if no paths are present.
|
|
2171
|
+
*/
|
|
2172
|
+
function buildPrefixesProp(fieldBits, fieldBitsHi, f) {
|
|
2173
|
+
if (fieldBits.size === 0 && fieldBitsHi.size === 0)
|
|
2174
|
+
return null;
|
|
2175
|
+
// Sort paths by bit value within each word. Bits are powers of two
|
|
2176
|
+
// inside their word (1, 2, 4, …, 1<<30), so sorting numerically gives
|
|
2177
|
+
// ascending bit position. FULL_MASK (-1) entries from past-61
|
|
2178
|
+
// overflow shouldn't drive a prefix entry — defensively skip them.
|
|
2179
|
+
const orderedLo = [...fieldBits.entries()]
|
|
2180
|
+
.filter(([, bit]) => bit > 0)
|
|
2181
|
+
.sort(([, a], [, b]) => a - b);
|
|
2182
|
+
const orderedHi = [...fieldBitsHi.entries()].sort(([, a], [, b]) => a - b);
|
|
2183
|
+
const buildArrow = (path) => {
|
|
2184
|
+
const parts = path.split('.');
|
|
2185
|
+
const body = buildAccess(f, 's', parts);
|
|
2186
|
+
return f.createArrowFunction(undefined, undefined, [f.createParameterDeclaration(undefined, undefined, 's')], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), body);
|
|
2187
|
+
};
|
|
2188
|
+
const arrows = [
|
|
2189
|
+
...orderedLo.map(([path]) => buildArrow(path)),
|
|
2190
|
+
...orderedHi.map(([path]) => buildArrow(path)),
|
|
2191
|
+
];
|
|
2192
|
+
return f.createPropertyAssignment('__prefixes', f.createArrayLiteralExpression(arrows, false));
|
|
2193
|
+
}
|
|
2109
2194
|
function buildAccess(f, root, parts) {
|
|
2110
2195
|
let expr = f.createIdentifier(root);
|
|
2111
2196
|
for (const part of parts) {
|
|
@@ -2817,7 +2902,7 @@ const VOID_ELEMENTS = new Set([
|
|
|
2817
2902
|
* Try to analyze an element call and all its descendants as a collapsible subtree.
|
|
2818
2903
|
* Returns null if any part of the tree is not eligible for collapse.
|
|
2819
2904
|
*/
|
|
2820
|
-
function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
2905
|
+
function analyzeSubtree(node, helpers, fieldBits, path, fieldBitsHi = new Map()) {
|
|
2821
2906
|
if (!ts.isIdentifier(node.expression))
|
|
2822
2907
|
return null;
|
|
2823
2908
|
const localName = node.expression.text;
|
|
@@ -2871,8 +2956,8 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2871
2956
|
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
2872
2957
|
const kind = classifyKind(key);
|
|
2873
2958
|
const resolvedKey = resolveKey(key, kind);
|
|
2874
|
-
const { mask, readsState } = computeAccessorMask(value, fieldBits);
|
|
2875
|
-
if (mask === 0 && !readsState) {
|
|
2959
|
+
const { mask, maskHi, readsState } = computeAccessorMask(value, fieldBits, undefined, fieldBitsHi);
|
|
2960
|
+
if (mask === 0 && maskHi === 0 && !readsState) {
|
|
2876
2961
|
// Constant fold — treat as static if we can extract a string
|
|
2877
2962
|
const staticVal = tryExtractStaticString(value);
|
|
2878
2963
|
if (staticVal !== null) {
|
|
@@ -2881,21 +2966,22 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2881
2966
|
continue;
|
|
2882
2967
|
}
|
|
2883
2968
|
}
|
|
2884
|
-
|
|
2969
|
+
const finalMask = mask === 0 && maskHi === 0 && readsState ? 0xffffffff | 0 : mask;
|
|
2970
|
+
bindings.push([finalMask, maskHi, kind, resolvedKey, value]);
|
|
2885
2971
|
continue;
|
|
2886
2972
|
}
|
|
2887
2973
|
// Per-item accessor call
|
|
2888
2974
|
if (ts.isCallExpression(value) && isPerItemCall(value)) {
|
|
2889
2975
|
const kind = classifyKind(key);
|
|
2890
2976
|
const resolvedKey = resolveKey(key, kind);
|
|
2891
|
-
bindings.push([0xffffffff | 0, kind, resolvedKey, value]);
|
|
2977
|
+
bindings.push([0xffffffff | 0, 0, kind, resolvedKey, value]);
|
|
2892
2978
|
continue;
|
|
2893
2979
|
}
|
|
2894
2980
|
// Per-item property access: item.field (or hoisted __a0/__a1/…)
|
|
2895
2981
|
if (isPerItemFieldAccess(value) || isHoistedPerItem(value)) {
|
|
2896
2982
|
const kind = classifyKind(key);
|
|
2897
2983
|
const resolvedKey = resolveKey(key, kind);
|
|
2898
|
-
bindings.push([0xffffffff | 0, kind, resolvedKey, value]);
|
|
2984
|
+
bindings.push([0xffffffff | 0, 0, kind, resolvedKey, value]);
|
|
2899
2985
|
continue;
|
|
2900
2986
|
}
|
|
2901
2987
|
// Static literal prop
|
|
@@ -2944,11 +3030,12 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2944
3030
|
// Reactive text — accessor is first arg
|
|
2945
3031
|
const accessor = child.arguments[0];
|
|
2946
3032
|
if (ts.isArrowFunction(accessor) || ts.isFunctionExpression(accessor)) {
|
|
2947
|
-
const { mask, readsState } = computeAccessorMask(accessor, fieldBits);
|
|
3033
|
+
const { mask, maskHi, readsState } = computeAccessorMask(accessor, fieldBits, undefined, fieldBitsHi);
|
|
2948
3034
|
children.push({
|
|
2949
3035
|
type: 'reactiveText',
|
|
2950
3036
|
accessor,
|
|
2951
|
-
mask: mask === 0 && readsState ? 0xffffffff | 0 : mask,
|
|
3037
|
+
mask: mask === 0 && maskHi === 0 && readsState ? 0xffffffff | 0 : mask,
|
|
3038
|
+
maskHi,
|
|
2952
3039
|
childIdx,
|
|
2953
3040
|
});
|
|
2954
3041
|
childIdx++; // placeholder text node in template
|
|
@@ -2960,6 +3047,7 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2960
3047
|
type: 'reactiveText',
|
|
2961
3048
|
accessor,
|
|
2962
3049
|
mask: 0xffffffff | 0,
|
|
3050
|
+
maskHi: 0,
|
|
2963
3051
|
childIdx,
|
|
2964
3052
|
});
|
|
2965
3053
|
childIdx++; // placeholder text node in template
|
|
@@ -2972,6 +3060,7 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2972
3060
|
type: 'reactiveText',
|
|
2973
3061
|
accessor,
|
|
2974
3062
|
mask: 0xffffffff | 0,
|
|
3063
|
+
maskHi: 0,
|
|
2975
3064
|
childIdx,
|
|
2976
3065
|
});
|
|
2977
3066
|
childIdx++;
|
|
@@ -2983,7 +3072,7 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2983
3072
|
if (ts.isCallExpression(child) &&
|
|
2984
3073
|
ts.isIdentifier(child.expression) &&
|
|
2985
3074
|
helpers.has(child.expression.text)) {
|
|
2986
|
-
const childNode = analyzeSubtree(child, helpers, fieldBits, [...path, childIdx]);
|
|
3075
|
+
const childNode = analyzeSubtree(child, helpers, fieldBits, [...path, childIdx], fieldBitsHi);
|
|
2987
3076
|
if (!childNode)
|
|
2988
3077
|
return null;
|
|
2989
3078
|
children.push({ type: 'element', node: childNode });
|
|
@@ -3196,24 +3285,33 @@ function emitSubtreeTemplate(analyzed, fieldBits, f) {
|
|
|
3196
3285
|
], ts.NodeFlags.Const)));
|
|
3197
3286
|
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createPropertyAccessExpression(f.createIdentifier(cVar), 'parentNode'), 'replaceChild'), undefined, [f.createIdentifier(tVar), f.createIdentifier(cVar)])));
|
|
3198
3287
|
}
|
|
3199
|
-
// __bind(__t0, mask, 'text', undefined, accessor)
|
|
3200
|
-
|
|
3288
|
+
// __bind(__t0, mask, 'text', undefined, accessor, [maskHi])
|
|
3289
|
+
const rtArgs = [
|
|
3201
3290
|
f.createIdentifier(tVar),
|
|
3202
3291
|
createMaskLiteral(f, rt.mask),
|
|
3203
3292
|
f.createStringLiteral('text'),
|
|
3204
3293
|
f.createIdentifier('undefined'),
|
|
3205
3294
|
rt.accessor,
|
|
3206
|
-
]
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3295
|
+
];
|
|
3296
|
+
// Only pass the 6th positional arg when the accessor reads a
|
|
3297
|
+
// high-word prefix. Keeps the emit byte-identical to the
|
|
3298
|
+
// pre-multi-word baseline for the common case.
|
|
3299
|
+
if (rt.maskHi !== 0)
|
|
3300
|
+
rtArgs.push(createMaskLiteral(f, rt.maskHi));
|
|
3301
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createIdentifier('__bind'), undefined, rtArgs)));
|
|
3302
|
+
}
|
|
3303
|
+
// Reactive bindings — __bind(node, mask, kind, key, accessor, [maskHi])
|
|
3304
|
+
for (const [mask, maskHi, kind, key, accessor] of op.bindings) {
|
|
3305
|
+
const args = [
|
|
3211
3306
|
nodeRef,
|
|
3212
3307
|
createMaskLiteral(f, mask),
|
|
3213
3308
|
f.createStringLiteral(kind),
|
|
3214
3309
|
key ? f.createStringLiteral(key) : f.createIdentifier('undefined'),
|
|
3215
3310
|
accessor,
|
|
3216
|
-
]
|
|
3311
|
+
];
|
|
3312
|
+
if (maskHi !== 0)
|
|
3313
|
+
args.push(createMaskLiteral(f, maskHi));
|
|
3314
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createIdentifier('__bind'), undefined, args)));
|
|
3217
3315
|
}
|
|
3218
3316
|
}
|
|
3219
3317
|
// Emit delegated event listeners on root
|
|
@@ -3427,22 +3525,23 @@ function isHoistedPerItem(node) {
|
|
|
3427
3525
|
// See `NON_DELEGATION_HELPERS` in collect-deps.ts — same set of names
|
|
3428
3526
|
// that aren't followed when scanning for `helper(s)` delegation calls.
|
|
3429
3527
|
const NON_DELEGATION_HELPERS = new Set(['sample', 'item', 'memo', 'text', 'unsafeHtml']);
|
|
3430
|
-
function computeAccessorMask(accessor, fieldBits, visited = new Set()) {
|
|
3528
|
+
function computeAccessorMask(accessor, fieldBits, visited = new Set(), fieldBitsHi) {
|
|
3431
3529
|
if (visited.has(accessor))
|
|
3432
|
-
return { mask: 0, readsState: false };
|
|
3530
|
+
return { mask: 0, maskHi: 0, readsState: false };
|
|
3433
3531
|
visited.add(accessor);
|
|
3434
3532
|
if (accessor.parameters.length === 0)
|
|
3435
|
-
return { mask: 0xffffffff | 0, readsState: false };
|
|
3533
|
+
return { mask: 0xffffffff | 0, maskHi: 0, readsState: false };
|
|
3436
3534
|
const paramName = accessor.parameters[0].name;
|
|
3437
3535
|
if (!ts.isIdentifier(paramName))
|
|
3438
|
-
return { mask: 0xffffffff | 0, readsState: false };
|
|
3536
|
+
return { mask: 0xffffffff | 0, maskHi: 0, readsState: false };
|
|
3439
3537
|
// FunctionDeclaration always has a body (we never resolve overloads here);
|
|
3440
3538
|
// ArrowFunction's body may be a single expression. Both shapes are walked
|
|
3441
3539
|
// identically by ts.forEachChild, so no special-casing is needed below.
|
|
3442
3540
|
if (!accessor.body)
|
|
3443
|
-
return { mask: 0xffffffff | 0, readsState: false };
|
|
3541
|
+
return { mask: 0xffffffff | 0, maskHi: 0, readsState: false };
|
|
3444
3542
|
const stateParam = paramName.text;
|
|
3445
3543
|
let mask = 0;
|
|
3544
|
+
let maskHi = 0;
|
|
3446
3545
|
let readsState = false;
|
|
3447
3546
|
// `inNestedFn` gates only the delegation-recursion. Property-access
|
|
3448
3547
|
// path extraction happens everywhere — inner-arrow callbacks like
|
|
@@ -3472,9 +3571,13 @@ function computeAccessorMask(accessor, fieldBits, visited = new Set()) {
|
|
|
3472
3571
|
const chain = resolveChain(node, stateParam);
|
|
3473
3572
|
if (chain) {
|
|
3474
3573
|
const bit = fieldBits.get(chain);
|
|
3574
|
+
const bitHi = fieldBitsHi?.get(chain);
|
|
3475
3575
|
if (bit !== undefined) {
|
|
3476
3576
|
mask |= bit;
|
|
3477
3577
|
}
|
|
3578
|
+
else if (bitHi !== undefined) {
|
|
3579
|
+
maskHi |= bitHi;
|
|
3580
|
+
}
|
|
3478
3581
|
else {
|
|
3479
3582
|
// Match paths that overlap our chain in either direction:
|
|
3480
3583
|
// - `path` extends `chain` — fieldBits has finer-grained paths
|
|
@@ -3490,6 +3593,15 @@ function computeAccessorMask(accessor, fieldBits, visited = new Set()) {
|
|
|
3490
3593
|
mask |= b;
|
|
3491
3594
|
}
|
|
3492
3595
|
}
|
|
3596
|
+
if (fieldBitsHi) {
|
|
3597
|
+
for (const [path, b] of fieldBitsHi) {
|
|
3598
|
+
if (path === chain ||
|
|
3599
|
+
path.startsWith(chain + '.') ||
|
|
3600
|
+
chain.startsWith(path + '.')) {
|
|
3601
|
+
maskHi |= b;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3493
3605
|
}
|
|
3494
3606
|
}
|
|
3495
3607
|
}
|
|
@@ -3506,8 +3618,9 @@ function computeAccessorMask(accessor, fieldBits, visited = new Set()) {
|
|
|
3506
3618
|
if (arg0 && ts.isIdentifier(arg0) && arg0.text === stateParam) {
|
|
3507
3619
|
const resolved = resolveAccessorBody(node.expression);
|
|
3508
3620
|
if (resolved) {
|
|
3509
|
-
const inner = computeAccessorMask(resolved, fieldBits, visited);
|
|
3621
|
+
const inner = computeAccessorMask(resolved, fieldBits, visited, fieldBitsHi);
|
|
3510
3622
|
mask |= inner.mask;
|
|
3623
|
+
maskHi |= inner.maskHi;
|
|
3511
3624
|
if (inner.readsState)
|
|
3512
3625
|
readsState = true;
|
|
3513
3626
|
}
|
|
@@ -3518,10 +3631,10 @@ function computeAccessorMask(accessor, fieldBits, visited = new Set()) {
|
|
|
3518
3631
|
ts.forEachChild(node, (child) => walk(child, inNestedFn || enteringNested));
|
|
3519
3632
|
}
|
|
3520
3633
|
walk(accessor.body, false);
|
|
3521
|
-
if (mask === 0 && readsState) {
|
|
3522
|
-
return { mask: 0xffffffff | 0, readsState: true };
|
|
3634
|
+
if (mask === 0 && maskHi === 0 && readsState) {
|
|
3635
|
+
return { mask: 0xffffffff | 0, maskHi: 0, readsState: true };
|
|
3523
3636
|
}
|
|
3524
|
-
return { mask, readsState };
|
|
3637
|
+
return { mask, maskHi, readsState };
|
|
3525
3638
|
}
|
|
3526
3639
|
function resolveChain(node, paramName) {
|
|
3527
3640
|
const parts = [];
|