@neocompose/cli 0.61.3 → 0.61.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.61.4] - 2026-10-01
4
+
5
+ - Narrow implicit instance members after null and type checks just like their `this.Member` spelling, including nested conditions, assignments, and loop invalidation.
6
+ - Keep symbol rename scoped to resolved references in test files, including declarations without an assigned ID.
7
+
3
8
  ## [0.61.3] - 2026-10-01
4
9
 
5
10
  - Resolve `collectionValue: Reference(root.Assets.Quests.Daily)` through construction defaults, including classes and empty collections declared in the same push. Preparation creates the required rows and associations together; no preliminary push or manually assigned value IDs are needed.
package/dist/neo.mjs CHANGED
@@ -5112,8 +5112,8 @@ function pathRoot(path) {
5112
5112
  const dot = path.indexOf(".");
5113
5113
  return dot === -1 ? path : path.slice(0, dot);
5114
5114
  }
5115
- function factsFromIsCheck(expression, whenTrue) {
5116
- const path = canonicalPath(expression.operand);
5115
+ function factsFromIsCheck(expression, whenTrue, pathOf) {
5116
+ const path = pathOf(expression.operand);
5117
5117
  const nullCheck = expression.type.kind === "primitive" && expression.type.name === "null";
5118
5118
  if (path === null && (!whenTrue || nullCheck || !expression.bindingName)) {
5119
5119
  return [];
@@ -5137,58 +5137,72 @@ function extractNullCheckSide(expression) {
5137
5137
  if (!leftNull && !rightNull) return null;
5138
5138
  return leftNull ? expression.right : expression.left;
5139
5139
  }
5140
- function factsWhenTrue(expression) {
5141
- if (expression.kind === "is") return factsFromIsCheck(expression, true);
5140
+ function pathsAfterBindings(pathOf, facts) {
5141
+ const bindings = new Set(
5142
+ facts.flatMap((fact) => fact.bindingName ? [fact.bindingName] : [])
5143
+ );
5144
+ if (bindings.size === 0) return pathOf;
5145
+ return (expression) => {
5146
+ const path = canonicalPath(expression);
5147
+ return path !== null && bindings.has(pathRoot(path)) ? path : pathOf(expression);
5148
+ };
5149
+ }
5150
+ function factsWhenTrue(expression, pathOf) {
5151
+ if (expression.kind === "is")
5152
+ return factsFromIsCheck(expression, true, pathOf);
5142
5153
  if (expression.kind === "binary") {
5143
5154
  if (expression.op === "&&") {
5155
+ const left = factsWhenTrue(expression.left, pathOf);
5144
5156
  return [
5145
- ...factsWhenTrue(expression.left),
5146
- ...factsWhenTrue(expression.right)
5157
+ ...left,
5158
+ ...factsWhenTrue(expression.right, pathsAfterBindings(pathOf, left))
5147
5159
  ];
5148
5160
  }
5149
5161
  if (expression.op === "||") {
5150
5162
  return mergeFactSets([
5151
- factsWhenTrue(expression.left),
5152
- factsWhenTrue(expression.right)
5163
+ factsWhenTrue(expression.left, pathOf),
5164
+ factsWhenTrue(expression.right, pathOf)
5153
5165
  ]);
5154
5166
  }
5155
5167
  if (expression.op === "==" || expression.op === "!=") {
5156
5168
  const value = extractNullCheckSide(expression);
5157
- const path2 = value ? canonicalPath(value) : null;
5169
+ const path2 = value ? pathOf(value) : null;
5158
5170
  return value && path2 ? [{ expression: value, path: path2, nullValue: expression.op === "==" }] : [];
5159
5171
  }
5160
5172
  }
5161
5173
  if (expression.kind === "unary" && expression.op === "!") {
5162
- return factsWhenFalse(expression.operand);
5174
+ return factsWhenFalse(expression.operand, pathOf);
5163
5175
  }
5164
- const path = canonicalPath(expression);
5176
+ const path = pathOf(expression);
5165
5177
  return path && (expression.kind === "ident" || expression.kind === "member") ? [{ expression, path, nullValue: false, loose: true }] : [];
5166
5178
  }
5167
- function factsWhenFalse(expression) {
5168
- if (expression.kind === "is") return factsFromIsCheck(expression, false);
5179
+ function factsWhenFalse(expression, pathOf) {
5180
+ if (expression.kind === "is")
5181
+ return factsFromIsCheck(expression, false, pathOf);
5169
5182
  if (expression.kind === "binary") {
5170
5183
  if (expression.op === "||") {
5184
+ const left = factsWhenFalse(expression.left, pathOf);
5171
5185
  return [
5172
- ...factsWhenFalse(expression.left),
5173
- ...factsWhenFalse(expression.right)
5186
+ ...left,
5187
+ ...factsWhenFalse(expression.right, pathsAfterBindings(pathOf, left))
5174
5188
  ];
5175
5189
  }
5176
5190
  if (expression.op === "&&") {
5177
5191
  return mergeFactSets([
5178
- factsWhenFalse(expression.left),
5179
- factsWhenFalse(expression.right)
5192
+ factsWhenFalse(expression.left, pathOf),
5193
+ factsWhenFalse(expression.right, pathOf)
5180
5194
  ]);
5181
5195
  }
5182
5196
  if (expression.op === "==" || expression.op === "!=") {
5183
5197
  const value = extractNullCheckSide(expression);
5184
- const path2 = value ? canonicalPath(value) : null;
5198
+ const path2 = value ? pathOf(value) : null;
5185
5199
  return value && path2 ? [{ expression: value, path: path2, nullValue: expression.op !== "==" }] : [];
5186
5200
  }
5187
5201
  }
5188
5202
  if (expression.kind === "unary" && expression.op === "!") {
5189
- return factsWhenTrue(expression.operand);
5203
+ return factsWhenTrue(expression.operand, pathOf);
5190
5204
  }
5191
- const path = canonicalPath(expression);
5205
+ const path = pathOf(expression);
5192
5206
  return path && (expression.kind === "ident" || expression.kind === "member") ? [{ expression, path, nullValue: true, loose: true }] : [];
5193
5207
  }
5194
5208
  function mergeFactSets(sets) {
@@ -5350,156 +5364,440 @@ function scopeVariable(name, value, sourceType, writability, ownership, writeRoo
5350
5364
  ...readonlyBinding ? { readonlyBinding } : {}
5351
5365
  };
5352
5366
  }
5353
- function invalidateLoopEntryNarrowingsAssignedBy(loopEntryScope, outerScope, statements) {
5354
- for (const rootName of collectAssignedPathRoots(statements)) {
5355
- const outerEntry = outerScope.lookup(rootName);
5356
- if (outerEntry) {
5357
- loopEntryScope.hideInheritedNarrowing(outerEntry);
5367
+ function statementFlow(statements, cache) {
5368
+ const cached = cache.get(statements);
5369
+ if (cached) return cached;
5370
+ const flow = /* @__PURE__ */ new Set(["fallthrough"]);
5371
+ for (const statement of statements) {
5372
+ if (!flow.delete("fallthrough")) break;
5373
+ let next;
5374
+ switch (statement.kind) {
5375
+ case "return":
5376
+ case "throw":
5377
+ next = /* @__PURE__ */ new Set(["exit"]);
5378
+ break;
5379
+ case "break":
5380
+ next = /* @__PURE__ */ new Set(["break"]);
5381
+ break;
5382
+ case "continue":
5383
+ next = /* @__PURE__ */ new Set(["continue"]);
5384
+ break;
5385
+ case "if":
5386
+ next = new Set(statementFlow(statement.elseBody ?? [], cache));
5387
+ for (const branch of statement.branches)
5388
+ for (const outcome of statementFlow(branch.body, cache))
5389
+ next.add(outcome);
5390
+ break;
5391
+ case "try":
5392
+ next = new Set(statementFlow(statement.body, cache));
5393
+ for (const clause of statement.catches)
5394
+ for (const outcome of statementFlow(clause.body, cache))
5395
+ next.add(outcome);
5396
+ break;
5397
+ case "switch":
5398
+ next = /* @__PURE__ */ new Set();
5399
+ if (!statement.sections.some(
5400
+ (section) => section.labels.some((label) => label.kind === "default")
5401
+ )) {
5402
+ next.add("fallthrough");
5403
+ }
5404
+ for (const section of statement.sections)
5405
+ for (const outcome of statementFlow(section.body, cache))
5406
+ next.add(outcome);
5407
+ if (next.delete("break")) next.add("fallthrough");
5408
+ break;
5409
+ case "doWhile":
5410
+ next = new Set(statementFlow(statement.body, cache));
5411
+ if (next.delete("break")) next.add("fallthrough");
5412
+ if (next.delete("continue")) next.add("fallthrough");
5413
+ break;
5414
+ default:
5415
+ next = /* @__PURE__ */ new Set(["fallthrough"]);
5416
+ break;
5358
5417
  }
5418
+ for (const outcome of next) flow.add(outcome);
5359
5419
  }
5420
+ cache.set(statements, flow);
5421
+ return flow;
5422
+ }
5423
+ function localsWithBindings(locals, expression, whenTrue) {
5424
+ const facts = whenTrue ? factsWhenTrue(expression, canonicalPath) : factsWhenFalse(expression, canonicalPath);
5425
+ return /* @__PURE__ */ new Set([
5426
+ ...locals,
5427
+ ...facts.flatMap((fact) => fact.bindingName ? [fact.bindingName] : [])
5428
+ ]);
5360
5429
  }
5361
- function collectAssignedPathRoots(statements, roots = /* @__PURE__ */ new Set()) {
5430
+ function collectAssignedPathRoots(statements, roots = /* @__PURE__ */ new Set(), inheritedLocals = /* @__PURE__ */ new Set(), flowCache = /* @__PURE__ */ new WeakMap()) {
5431
+ const locals = new Set(inheritedLocals);
5362
5432
  for (const statement of statements) {
5363
5433
  if (statement.kind === "assign") {
5364
5434
  const assignedPath = canonicalPath(statement.target);
5365
- if (assignedPath) roots.add(pathRoot(assignedPath));
5366
- collectAssignedPathRootsInExpression(statement.target, roots);
5435
+ if (assignedPath && !locals.has(pathRoot(assignedPath))) {
5436
+ roots.add(pathRoot(assignedPath));
5437
+ }
5438
+ collectAssignedPathRootsInExpression(
5439
+ statement.target,
5440
+ roots,
5441
+ locals,
5442
+ flowCache
5443
+ );
5367
5444
  if (statement.value)
5368
- collectAssignedPathRootsInExpression(statement.value, roots);
5445
+ collectAssignedPathRootsInExpression(
5446
+ statement.value,
5447
+ roots,
5448
+ locals,
5449
+ flowCache
5450
+ );
5369
5451
  continue;
5370
5452
  }
5371
5453
  if (statement.kind === "varDecl") {
5372
- collectAssignedPathRootsInExpression(statement.init, roots);
5454
+ collectAssignedPathRootsInExpression(
5455
+ statement.init,
5456
+ roots,
5457
+ locals,
5458
+ flowCache
5459
+ );
5460
+ locals.add(statement.name);
5373
5461
  continue;
5374
5462
  }
5375
5463
  if (statement.kind === "if") {
5464
+ let falseLocals = locals;
5465
+ const falseFacts = [];
5466
+ const fallthroughFacts = [];
5376
5467
  for (const branch of statement.branches) {
5377
- collectAssignedPathRootsInExpression(branch.cond, roots);
5378
- collectAssignedPathRoots(branch.body, roots);
5468
+ collectAssignedPathRootsInExpression(
5469
+ branch.cond,
5470
+ roots,
5471
+ falseLocals,
5472
+ flowCache
5473
+ );
5474
+ const branchFacts = [
5475
+ ...falseFacts,
5476
+ ...factsWhenTrue(branch.cond, canonicalPath)
5477
+ ];
5478
+ collectAssignedPathRoots(
5479
+ branch.body,
5480
+ roots,
5481
+ localsWithBindings(falseLocals, branch.cond, true),
5482
+ flowCache
5483
+ );
5484
+ if (statementFlow(branch.body, flowCache).has("fallthrough"))
5485
+ fallthroughFacts.push(branchFacts);
5486
+ falseFacts.push(...factsWhenFalse(branch.cond, canonicalPath));
5487
+ falseLocals = localsWithBindings(falseLocals, branch.cond, false);
5379
5488
  }
5380
5489
  if (statement.elseBody) {
5381
- collectAssignedPathRoots(statement.elseBody, roots);
5490
+ collectAssignedPathRoots(
5491
+ statement.elseBody,
5492
+ roots,
5493
+ falseLocals,
5494
+ flowCache
5495
+ );
5496
+ if (statementFlow(statement.elseBody, flowCache).has("fallthrough"))
5497
+ fallthroughFacts.push(falseFacts);
5498
+ } else {
5499
+ fallthroughFacts.push(falseFacts);
5500
+ }
5501
+ for (const fact of mergeFactSets(fallthroughFacts)) {
5502
+ if (fact.bindingName) locals.add(fact.bindingName);
5382
5503
  }
5383
5504
  continue;
5384
5505
  }
5385
5506
  if (statement.kind === "try") {
5386
- collectAssignedPathRoots(statement.body, roots);
5507
+ collectAssignedPathRoots(statement.body, roots, locals, flowCache);
5387
5508
  for (const clause of statement.catches) {
5509
+ const catchLocals = /* @__PURE__ */ new Set([...locals, clause.name]);
5388
5510
  if (clause.filter) {
5389
- collectAssignedPathRootsInExpression(clause.filter, roots);
5511
+ collectAssignedPathRootsInExpression(
5512
+ clause.filter,
5513
+ roots,
5514
+ catchLocals,
5515
+ flowCache
5516
+ );
5390
5517
  }
5391
- collectAssignedPathRoots(clause.body, roots);
5518
+ collectAssignedPathRoots(
5519
+ clause.body,
5520
+ roots,
5521
+ clause.filter ? localsWithBindings(catchLocals, clause.filter, true) : catchLocals,
5522
+ flowCache
5523
+ );
5392
5524
  }
5393
5525
  continue;
5394
5526
  }
5395
5527
  if (statement.kind === "while" || statement.kind === "doWhile") {
5396
- collectAssignedPathRootsInExpression(statement.condition, roots);
5397
- collectAssignedPathRoots(statement.body, roots);
5528
+ collectAssignedPathRootsInExpression(
5529
+ statement.condition,
5530
+ roots,
5531
+ locals,
5532
+ flowCache
5533
+ );
5534
+ collectAssignedPathRoots(
5535
+ statement.body,
5536
+ roots,
5537
+ statement.kind === "while" ? localsWithBindings(locals, statement.condition, true) : locals,
5538
+ flowCache
5539
+ );
5398
5540
  continue;
5399
5541
  }
5400
5542
  if (statement.kind === "for") {
5401
- collectAssignedPathRootsInExpression(statement.initializer.init, roots);
5402
- collectAssignedPathRootsInExpression(statement.condition, roots);
5403
- collectAssignedPathRoots([statement.iterator], roots);
5404
- collectAssignedPathRoots(statement.body, roots);
5543
+ collectAssignedPathRootsInExpression(
5544
+ statement.initializer.init,
5545
+ roots,
5546
+ locals,
5547
+ flowCache
5548
+ );
5549
+ const loopLocals = /* @__PURE__ */ new Set([...locals, statement.initializer.name]);
5550
+ collectAssignedPathRootsInExpression(
5551
+ statement.condition,
5552
+ roots,
5553
+ loopLocals,
5554
+ flowCache
5555
+ );
5556
+ const iterationLocals = localsWithBindings(
5557
+ loopLocals,
5558
+ statement.condition,
5559
+ true
5560
+ );
5561
+ collectAssignedPathRoots(
5562
+ [statement.iterator],
5563
+ roots,
5564
+ iterationLocals,
5565
+ flowCache
5566
+ );
5567
+ collectAssignedPathRoots(
5568
+ statement.body,
5569
+ roots,
5570
+ iterationLocals,
5571
+ flowCache
5572
+ );
5405
5573
  continue;
5406
5574
  }
5407
5575
  if (statement.kind === "forEach") {
5408
- collectAssignedPathRootsInExpression(statement.collection, roots);
5409
- collectAssignedPathRoots(statement.body, roots);
5576
+ collectAssignedPathRootsInExpression(
5577
+ statement.collection,
5578
+ roots,
5579
+ locals,
5580
+ flowCache
5581
+ );
5582
+ const loopLocals = /* @__PURE__ */ new Set([...locals, statement.name]);
5583
+ if (statement.key) loopLocals.add(statement.key.name);
5584
+ collectAssignedPathRoots(statement.body, roots, loopLocals, flowCache);
5410
5585
  continue;
5411
5586
  }
5412
5587
  if (statement.kind === "switch") {
5413
- collectAssignedPathRootsInExpression(statement.selector, roots);
5588
+ collectAssignedPathRootsInExpression(
5589
+ statement.selector,
5590
+ roots,
5591
+ locals,
5592
+ flowCache
5593
+ );
5414
5594
  for (const section of statement.sections) {
5415
5595
  for (const label of section.labels) {
5416
5596
  if (label.kind === "case") {
5417
- collectAssignedPathRootsInExpression(label.expression, roots);
5597
+ collectAssignedPathRootsInExpression(
5598
+ label.expression,
5599
+ roots,
5600
+ locals,
5601
+ flowCache
5602
+ );
5418
5603
  }
5419
5604
  }
5420
- collectAssignedPathRoots(section.body, roots);
5605
+ collectAssignedPathRoots(section.body, roots, locals, flowCache);
5421
5606
  }
5422
5607
  continue;
5423
5608
  }
5424
5609
  if (statement.kind === "return" || statement.kind === "throw") {
5425
5610
  if (statement.expr)
5426
- collectAssignedPathRootsInExpression(statement.expr, roots);
5611
+ collectAssignedPathRootsInExpression(
5612
+ statement.expr,
5613
+ roots,
5614
+ locals,
5615
+ flowCache
5616
+ );
5427
5617
  continue;
5428
5618
  }
5429
5619
  if (statement.kind === "exprStmt") {
5430
- collectAssignedPathRootsInExpression(statement.expr, roots);
5620
+ collectAssignedPathRootsInExpression(
5621
+ statement.expr,
5622
+ roots,
5623
+ locals,
5624
+ flowCache
5625
+ );
5431
5626
  }
5432
5627
  }
5433
5628
  return roots;
5434
5629
  }
5435
- function collectAssignedPathRootsInExpression(expression, roots) {
5630
+ function collectAssignedPathRootsInExpression(expression, roots, locals, flowCache) {
5436
5631
  switch (expression.kind) {
5437
5632
  case "litInterp":
5438
5633
  for (const part of expression.parts) {
5439
5634
  if (part.kind === "expr") {
5440
- collectAssignedPathRootsInExpression(part.expr, roots);
5635
+ collectAssignedPathRootsInExpression(
5636
+ part.expr,
5637
+ roots,
5638
+ locals,
5639
+ flowCache
5640
+ );
5441
5641
  }
5442
5642
  }
5443
5643
  return;
5444
5644
  case "litList":
5445
5645
  for (const element of expression.elements) {
5446
- collectAssignedPathRootsInExpression(element, roots);
5646
+ collectAssignedPathRootsInExpression(element, roots, locals, flowCache);
5447
5647
  }
5448
5648
  return;
5449
5649
  case "litDict":
5450
5650
  for (const entry of expression.entries) {
5451
- collectAssignedPathRootsInExpression(entry.key, roots);
5452
- collectAssignedPathRootsInExpression(entry.value, roots);
5651
+ collectAssignedPathRootsInExpression(
5652
+ entry.key,
5653
+ roots,
5654
+ locals,
5655
+ flowCache
5656
+ );
5657
+ collectAssignedPathRootsInExpression(
5658
+ entry.value,
5659
+ roots,
5660
+ locals,
5661
+ flowCache
5662
+ );
5453
5663
  }
5454
5664
  return;
5455
5665
  case "member":
5456
- collectAssignedPathRootsInExpression(expression.receiver, roots);
5666
+ collectAssignedPathRootsInExpression(
5667
+ expression.receiver,
5668
+ roots,
5669
+ locals,
5670
+ flowCache
5671
+ );
5457
5672
  return;
5458
5673
  case "index":
5459
- collectAssignedPathRootsInExpression(expression.receiver, roots);
5460
- collectAssignedPathRootsInExpression(expression.index, roots);
5674
+ collectAssignedPathRootsInExpression(
5675
+ expression.receiver,
5676
+ roots,
5677
+ locals,
5678
+ flowCache
5679
+ );
5680
+ collectAssignedPathRootsInExpression(
5681
+ expression.index,
5682
+ roots,
5683
+ locals,
5684
+ flowCache
5685
+ );
5461
5686
  return;
5462
5687
  case "call":
5463
- collectAssignedPathRootsInExpression(expression.callee, roots);
5688
+ collectAssignedPathRootsInExpression(
5689
+ expression.callee,
5690
+ roots,
5691
+ locals,
5692
+ flowCache
5693
+ );
5464
5694
  for (const argument2 of expression.args) {
5465
- collectAssignedPathRootsInExpression(argument2, roots);
5695
+ collectAssignedPathRootsInExpression(
5696
+ argument2,
5697
+ roots,
5698
+ locals,
5699
+ flowCache
5700
+ );
5466
5701
  }
5467
5702
  return;
5468
5703
  case "new":
5469
5704
  for (const argument2 of expression.args) {
5470
- collectAssignedPathRootsInExpression(argument2, roots);
5705
+ collectAssignedPathRootsInExpression(
5706
+ argument2,
5707
+ roots,
5708
+ locals,
5709
+ flowCache
5710
+ );
5471
5711
  }
5472
5712
  for (const initializer of expression.initializer ?? []) {
5473
- collectAssignedPathRootsInExpression(initializer.value, roots);
5713
+ collectAssignedPathRootsInExpression(
5714
+ initializer.value,
5715
+ roots,
5716
+ locals,
5717
+ flowCache
5718
+ );
5474
5719
  }
5475
5720
  return;
5476
5721
  case "annotated":
5477
5722
  for (const annotation2 of expression.annotations) {
5478
5723
  for (const argument2 of annotation2.args) {
5479
- collectAssignedPathRootsInExpression(argument2, roots);
5724
+ collectAssignedPathRootsInExpression(
5725
+ argument2,
5726
+ roots,
5727
+ locals,
5728
+ flowCache
5729
+ );
5480
5730
  }
5481
5731
  }
5482
- collectAssignedPathRootsInExpression(expression.expression, roots);
5732
+ collectAssignedPathRootsInExpression(
5733
+ expression.expression,
5734
+ roots,
5735
+ locals,
5736
+ flowCache
5737
+ );
5483
5738
  return;
5484
5739
  case "binary":
5485
5740
  case "coalesce":
5486
- collectAssignedPathRootsInExpression(expression.left, roots);
5487
- collectAssignedPathRootsInExpression(expression.right, roots);
5741
+ collectAssignedPathRootsInExpression(
5742
+ expression.left,
5743
+ roots,
5744
+ locals,
5745
+ flowCache
5746
+ );
5747
+ collectAssignedPathRootsInExpression(
5748
+ expression.right,
5749
+ roots,
5750
+ locals,
5751
+ flowCache
5752
+ );
5488
5753
  return;
5489
5754
  case "conditional":
5490
- collectAssignedPathRootsInExpression(expression.condition, roots);
5491
- collectAssignedPathRootsInExpression(expression.whenTrue, roots);
5492
- collectAssignedPathRootsInExpression(expression.whenFalse, roots);
5755
+ collectAssignedPathRootsInExpression(
5756
+ expression.condition,
5757
+ roots,
5758
+ locals,
5759
+ flowCache
5760
+ );
5761
+ collectAssignedPathRootsInExpression(
5762
+ expression.whenTrue,
5763
+ roots,
5764
+ locals,
5765
+ flowCache
5766
+ );
5767
+ collectAssignedPathRootsInExpression(
5768
+ expression.whenFalse,
5769
+ roots,
5770
+ locals,
5771
+ flowCache
5772
+ );
5493
5773
  return;
5494
5774
  case "unary":
5495
5775
  case "force":
5496
- collectAssignedPathRootsInExpression(expression.operand, roots);
5776
+ collectAssignedPathRootsInExpression(
5777
+ expression.operand,
5778
+ roots,
5779
+ locals,
5780
+ flowCache
5781
+ );
5497
5782
  return;
5498
5783
  case "is":
5499
- collectAssignedPathRootsInExpression(expression.operand, roots);
5784
+ collectAssignedPathRootsInExpression(
5785
+ expression.operand,
5786
+ roots,
5787
+ locals,
5788
+ flowCache
5789
+ );
5500
5790
  return;
5501
5791
  case "lambda":
5502
- collectAssignedPathRoots(expression.body, roots);
5792
+ collectAssignedPathRoots(
5793
+ expression.body,
5794
+ roots,
5795
+ /* @__PURE__ */ new Set([
5796
+ ...locals,
5797
+ ...expression.params.map((parameter4) => parameter4.name)
5798
+ ]),
5799
+ flowCache
5800
+ );
5503
5801
  return;
5504
5802
  case "litNull":
5505
5803
  case "litBool":
@@ -6846,16 +7144,18 @@ var init_strict_resolver = __esm({
6846
7144
  const child = new Scope(scope);
6847
7145
  const branchFacts = [
6848
7146
  ...cumulativeFalseFacts,
6849
- ...factsWhenTrue(branch.cond)
7147
+ ...this.conditionFacts(branch.cond, scope, true)
6850
7148
  ];
6851
- this.applyFactsToScope(scope, child, branchFacts);
7149
+ this.applyFactsToScope(child, branchFacts);
6852
7150
  const instructions = this.resolveStatements(branch.body, child);
6853
7151
  if (!instructionsTerminate(instructions)) {
6854
7152
  branchExitFacts.push(branchFacts);
6855
7153
  branchExitNarrowings.push(new Map(child.narrowedPaths));
6856
7154
  branchExitOwnershipScopes.push(child);
6857
7155
  }
6858
- cumulativeFalseFacts.push(...factsWhenFalse(branch.cond));
7156
+ cumulativeFalseFacts.push(
7157
+ ...this.conditionFacts(branch.cond, scope, false)
7158
+ );
6859
7159
  return {
6860
7160
  expression: pointerToBooleanExpression(condition.pointer),
6861
7161
  instructions
@@ -6864,7 +7164,7 @@ var init_strict_resolver = __esm({
6864
7164
  let otherwise = null;
6865
7165
  if (statement.elseBody) {
6866
7166
  const elseScope = new Scope(scope);
6867
- this.applyFactsToScope(scope, elseScope, cumulativeFalseFacts);
7167
+ this.applyFactsToScope(elseScope, cumulativeFalseFacts);
6868
7168
  otherwise = this.resolveStatements(statement.elseBody, elseScope);
6869
7169
  if (!instructionsTerminate(otherwise)) {
6870
7170
  branchExitFacts.push([...cumulativeFalseFacts]);
@@ -6873,7 +7173,7 @@ var init_strict_resolver = __esm({
6873
7173
  }
6874
7174
  } else {
6875
7175
  const noBranchScope = new Scope(scope);
6876
- this.applyFactsToScope(scope, noBranchScope, cumulativeFalseFacts);
7176
+ this.applyFactsToScope(noBranchScope, cumulativeFalseFacts);
6877
7177
  branchExitFacts.push([...cumulativeFalseFacts]);
6878
7178
  branchExitNarrowings.push(new Map(noBranchScope.narrowedPaths));
6879
7179
  branchExitOwnershipScopes.push(noBranchScope);
@@ -6891,7 +7191,6 @@ var init_strict_resolver = __esm({
6891
7191
  }
6892
7192
  mergeBranchOwnership(scope, branchExitOwnershipScopes);
6893
7193
  this.applyFactsToScope(
6894
- scope,
6895
7194
  scope,
6896
7195
  mergeFactSets(branchExitFacts),
6897
7196
  "bindings-only"
@@ -7001,10 +7300,11 @@ var init_strict_resolver = __esm({
7001
7300
  case "while":
7002
7301
  case "doWhile": {
7003
7302
  const loopScope = new Scope(scope);
7004
- invalidateLoopEntryNarrowingsAssignedBy(
7303
+ this.invalidateLoopEntryNarrowingsAssignedBy(
7005
7304
  loopScope,
7006
7305
  scope,
7007
- statement.body
7306
+ statement.body,
7307
+ statement.kind === "while" ? localsWithBindings(/* @__PURE__ */ new Set(), statement.condition, true) : /* @__PURE__ */ new Set()
7008
7308
  );
7009
7309
  const iterationScope = new Scope(loopScope);
7010
7310
  let condition;
@@ -7013,8 +7313,8 @@ var init_strict_resolver = __esm({
7013
7313
  this.resolveExpression(statement.condition, loopScope),
7014
7314
  statement.condition.pos
7015
7315
  );
7016
- this.applyFactsToScope(loopScope, iterationScope, [
7017
- ...factsWhenTrue(statement.condition)
7316
+ this.applyFactsToScope(iterationScope, [
7317
+ ...this.conditionFacts(statement.condition, loopScope, true)
7018
7318
  ]);
7019
7319
  }
7020
7320
  const bodyScope = new Scope(iterationScope);
@@ -7051,15 +7351,19 @@ var init_strict_resolver = __esm({
7051
7351
  );
7052
7352
  }
7053
7353
  const normalExitScope = new Scope(nextConditionScope);
7054
- this.applyFactsToScope(nextConditionScope, normalExitScope, [
7055
- ...factsWhenFalse(statement.condition)
7354
+ this.applyFactsToScope(normalExitScope, [
7355
+ ...this.conditionFacts(
7356
+ statement.condition,
7357
+ nextConditionScope,
7358
+ false
7359
+ )
7056
7360
  ]);
7057
7361
  const effectScopes = nextConditionEffects.length > 0 ? [nextConditionScope] : [];
7058
7362
  const exitNarrowings = nextConditionEffects.length > 0 ? [normalExitScope.effectiveNarrowedPaths()] : [];
7059
7363
  if (statement.kind === "while") {
7060
7364
  const initialExitScope = new Scope(loopScope);
7061
- this.applyFactsToScope(loopScope, initialExitScope, [
7062
- ...factsWhenFalse(statement.condition)
7365
+ this.applyFactsToScope(initialExitScope, [
7366
+ ...this.conditionFacts(statement.condition, loopScope, false)
7063
7367
  ]);
7064
7368
  effectScopes.push(loopScope);
7065
7369
  exitNarrowings.push(initialExitScope.effectiveNarrowedPaths());
@@ -7087,17 +7391,30 @@ var init_strict_resolver = __esm({
7087
7391
  );
7088
7392
  }
7089
7393
  const loopEntryScope = new Scope(loopScope);
7090
- invalidateLoopEntryNarrowingsAssignedBy(loopEntryScope, scope, [
7091
- ...statement.body,
7092
- statement.iterator
7093
- ]);
7394
+ const conditionLocals = localsWithBindings(
7395
+ /* @__PURE__ */ new Set(),
7396
+ statement.condition,
7397
+ true
7398
+ );
7399
+ this.invalidateLoopEntryNarrowingsAssignedBy(
7400
+ loopEntryScope,
7401
+ loopScope,
7402
+ statement.body,
7403
+ conditionLocals
7404
+ );
7405
+ this.invalidateLoopEntryNarrowingsAssignedBy(
7406
+ loopEntryScope,
7407
+ loopScope,
7408
+ [statement.iterator],
7409
+ conditionLocals
7410
+ );
7094
7411
  const condition = this.toBool(
7095
7412
  this.resolveExpression(statement.condition, loopEntryScope),
7096
7413
  statement.condition.pos
7097
7414
  );
7098
7415
  const iterationScope = new Scope(loopEntryScope);
7099
- this.applyFactsToScope(loopEntryScope, iterationScope, [
7100
- ...factsWhenTrue(statement.condition)
7416
+ this.applyFactsToScope(iterationScope, [
7417
+ ...this.conditionFacts(statement.condition, loopEntryScope, true)
7101
7418
  ]);
7102
7419
  const bodyScope = new Scope(iterationScope);
7103
7420
  const loopFlow = {
@@ -7133,8 +7450,8 @@ var init_strict_resolver = __esm({
7133
7450
  );
7134
7451
  }
7135
7452
  const initialConditionFalseScope = new Scope(loopScope);
7136
- this.applyFactsToScope(loopScope, initialConditionFalseScope, [
7137
- ...factsWhenFalse(statement.condition)
7453
+ this.applyFactsToScope(initialConditionFalseScope, [
7454
+ ...this.conditionFacts(statement.condition, loopScope, false)
7138
7455
  ]);
7139
7456
  const effectScopes = [loopScope];
7140
7457
  const normalExitScopes = [initialConditionFalseScope];
@@ -7145,11 +7462,13 @@ var init_strict_resolver = __esm({
7145
7462
  ]);
7146
7463
  effectScopes.push(postIterationScope);
7147
7464
  const subsequentConditionFalseScope = new Scope(postIterationScope);
7148
- this.applyFactsToScope(
7149
- postIterationScope,
7150
- subsequentConditionFalseScope,
7151
- [...factsWhenFalse(statement.condition)]
7152
- );
7465
+ this.applyFactsToScope(subsequentConditionFalseScope, [
7466
+ ...this.conditionFacts(
7467
+ statement.condition,
7468
+ postIterationScope,
7469
+ false
7470
+ )
7471
+ ]);
7153
7472
  normalExitScopes.push(subsequentConditionFalseScope);
7154
7473
  }
7155
7474
  this.mergeLoopEffects(scope, effectScopes, loopFlow.breakEffects);
@@ -7269,9 +7588,9 @@ var init_strict_resolver = __esm({
7269
7588
  });
7270
7589
  }
7271
7590
  const iterationScope = new Scope(loopScope);
7272
- invalidateLoopEntryNarrowingsAssignedBy(
7591
+ this.invalidateLoopEntryNarrowingsAssignedBy(
7273
7592
  iterationScope,
7274
- scope,
7593
+ loopScope,
7275
7594
  statement.body
7276
7595
  );
7277
7596
  const bodyScope = new Scope(iterationScope);
@@ -7563,8 +7882,8 @@ var init_strict_resolver = __esm({
7563
7882
  }
7564
7883
  const bodyScope = new Scope(catchScope);
7565
7884
  if (clause.filter) {
7566
- this.applyFactsToScope(catchScope, bodyScope, [
7567
- ...factsWhenTrue(clause.filter)
7885
+ this.applyFactsToScope(bodyScope, [
7886
+ ...this.conditionFacts(clause.filter, catchScope, true)
7568
7887
  ]);
7569
7888
  }
7570
7889
  const catchPointer = {
@@ -7857,7 +8176,10 @@ var init_strict_resolver = __esm({
7857
8176
  value.writability ?? "runtime" /* Runtime */
7858
8177
  );
7859
8178
  }
7860
- const assignedPath = canonicalPath(statement.target);
8179
+ const assignedPath = this.narrowingPath(
8180
+ canonicalPath(statement.target),
8181
+ scope
8182
+ );
7861
8183
  if (assignedPath) {
7862
8184
  const rootEntry = scope.lookup(pathRoot(assignedPath));
7863
8185
  if (rootEntry) {
@@ -7896,7 +8218,20 @@ var init_strict_resolver = __esm({
7896
8218
  * lookup `resolveMember` performs before it overlays a narrowed type.
7897
8219
  */
7898
8220
  assignmentStorageType(targetAst, resolvedTarget, local, scope) {
7899
- if (targetAst.kind === "ident") return local?.type ?? resolvedTarget.type;
8221
+ if (targetAst.kind === "ident") {
8222
+ if (local) return local.type;
8223
+ const path = this.narrowingPath(targetAst.name, scope);
8224
+ if (path === `this.${targetAst.name}`) {
8225
+ return this.resolveMemberNatural(
8226
+ { kind: "ident", name: "this", pos: targetAst.pos },
8227
+ targetAst.name,
8228
+ scope,
8229
+ targetAst.pos,
8230
+ false
8231
+ ).type;
8232
+ }
8233
+ return resolvedTarget.type;
8234
+ }
7900
8235
  if (targetAst.kind !== "member") return resolvedTarget.type;
7901
8236
  return this.resolveMemberNatural(
7902
8237
  targetAst.receiver,
@@ -7957,7 +8292,10 @@ var init_strict_resolver = __esm({
7957
8292
  actionType,
7958
8293
  scope
7959
8294
  );
7960
- const assignedPath = canonicalPath(statement.target);
8295
+ const assignedPath = this.narrowingPath(
8296
+ canonicalPath(statement.target),
8297
+ scope
8298
+ );
7961
8299
  if (assignedPath) {
7962
8300
  const rootEntry = scope.lookup(pathRoot(assignedPath));
7963
8301
  if (rootEntry) {
@@ -9108,11 +9446,42 @@ var init_strict_resolver = __esm({
9108
9446
  pos
9109
9447
  );
9110
9448
  }
9111
- applyFactsToScope(outerScope, targetScope, facts, mode = "all") {
9449
+ narrowingPath(path, scope) {
9450
+ if (path === null) return null;
9451
+ const root = pathRoot(path);
9452
+ if (scope.lookup(root)) return path;
9453
+ if (this.context.project.globals.some((symbol) => symbol.name === root)) {
9454
+ return path;
9455
+ }
9456
+ const implicit = this.implicitMember(root);
9457
+ return implicit && implicit.symbol.static !== true ? `this.${path}` : path;
9458
+ }
9459
+ conditionFacts(expression, scope, whenTrue) {
9460
+ const pathOf = (value) => this.narrowingPath(canonicalPath(value), scope);
9461
+ return whenTrue ? factsWhenTrue(expression, pathOf) : factsWhenFalse(expression, pathOf);
9462
+ }
9463
+ /**
9464
+ * A loop body is resolved once but can execute repeatedly. Any narrowing rooted
9465
+ * in an outer binding that the body or iterator may write is therefore stale
9466
+ * at a later iteration entry. Invalidate those roots before resolving the
9467
+ * shared iteration so only the loop's own condition can narrow them again.
9468
+ */
9469
+ invalidateLoopEntryNarrowingsAssignedBy(loopEntryScope, outerScope, statements, locals = /* @__PURE__ */ new Set()) {
9470
+ for (const rootName of collectAssignedPathRoots(
9471
+ statements,
9472
+ /* @__PURE__ */ new Set(),
9473
+ locals
9474
+ )) {
9475
+ const path = this.narrowingPath(rootName, outerScope);
9476
+ const outerEntry = path === null ? null : outerScope.lookup(pathRoot(path));
9477
+ if (outerEntry) loopEntryScope.hideInheritedNarrowing(outerEntry);
9478
+ }
9479
+ }
9480
+ applyFactsToScope(targetScope, facts, mode = "all") {
9112
9481
  for (const fact of facts) {
9113
9482
  if (mode === "bindings-only" && !fact.bindingName) continue;
9114
- const natural = this.resolveExpression(fact.expression, outerScope);
9115
- const rootEntry = fact.path === null ? null : outerScope.lookup(pathRoot(fact.path));
9483
+ const natural = this.resolveExpression(fact.expression, targetScope);
9484
+ const rootEntry = fact.path === null ? null : targetScope.lookup(pathRoot(fact.path));
9116
9485
  if (fact.nullValue) {
9117
9486
  if (fact.path === null || rootEntry === null) continue;
9118
9487
  if (fact.loose === true && natural.type.kind === "primitive" && (natural.type.name === "string" || natural.type.name === "int" || natural.type.name === "float" || natural.type.name === "bool")) {
@@ -9418,7 +9787,10 @@ var init_strict_resolver = __esm({
9418
9787
  optional2
9419
9788
  );
9420
9789
  if (!optional2) {
9421
- const receiverPath = canonicalPath(receiverAst);
9790
+ const receiverPath = this.narrowingPath(
9791
+ canonicalPath(receiverAst),
9792
+ scope
9793
+ );
9422
9794
  if (receiverPath) {
9423
9795
  const rootEntry = scope.lookup(pathRoot(receiverPath));
9424
9796
  if (rootEntry) {
@@ -12507,7 +12879,10 @@ var init_strict_resolver = __esm({
12507
12879
  scope,
12508
12880
  true
12509
12881
  );
12510
- const path = canonicalPath(expression.callee.receiver);
12882
+ const path = this.narrowingPath(
12883
+ canonicalPath(expression.callee.receiver),
12884
+ scope
12885
+ );
12511
12886
  if (path) {
12512
12887
  const rootEntry = scope.lookup(pathRoot(path));
12513
12888
  if (rootEntry) scope.invalidate(rootEntry);
@@ -12568,15 +12943,13 @@ var init_strict_resolver = __esm({
12568
12943
  );
12569
12944
  const trueScope = new Scope(scope);
12570
12945
  this.applyFactsToScope(
12571
- scope,
12572
12946
  trueScope,
12573
- factsWhenTrue(expression.condition)
12947
+ this.conditionFacts(expression.condition, scope, true)
12574
12948
  );
12575
12949
  const falseScope = new Scope(scope);
12576
12950
  this.applyFactsToScope(
12577
- scope,
12578
12951
  falseScope,
12579
- factsWhenFalse(expression.condition)
12952
+ this.conditionFacts(expression.condition, scope, false)
12580
12953
  );
12581
12954
  let whenTrue;
12582
12955
  let whenFalse;
@@ -12658,9 +13031,8 @@ var init_strict_resolver = __esm({
12658
13031
  if (expression.op === "&&" || expression.op === "||") {
12659
13032
  rightScope = new Scope(scope);
12660
13033
  this.applyFactsToScope(
12661
- scope,
12662
13034
  rightScope,
12663
- expression.op === "&&" ? factsWhenTrue(expression.left) : factsWhenFalse(expression.left)
13035
+ expression.op === "&&" ? this.conditionFacts(expression.left, scope, true) : this.conditionFacts(expression.left, scope, false)
12664
13036
  );
12665
13037
  }
12666
13038
  const right = preResolvedRight ?? this.resolveExpression(expression.right, rightScope, left.type);
@@ -35212,9 +35584,14 @@ var init_service = __esm({
35212
35584
  if (state.context.projectSourceKind !== "spec") continue;
35213
35585
  const snapshot = this.scriptSnapshot(state);
35214
35586
  for (const candidate of snapshot.parsed.references) {
35215
- if (resolveReferenceSymbolId(snapshot, candidate) !== declaration.id) {
35216
- continue;
35217
- }
35587
+ if (candidate.name !== declaration.name) continue;
35588
+ const symbolId = resolveReferenceSymbolId(snapshot, candidate);
35589
+ if (symbolId === null) continue;
35590
+ const matches = declaration.id !== null ? symbolId === declaration.id : sameLocationInList(
35591
+ declaration.location,
35592
+ definition(snapshot, candidate.range.start)
35593
+ );
35594
+ if (!matches) continue;
35218
35595
  locations.push({ uri: state.document.uri, range: candidate.range });
35219
35596
  }
35220
35597
  }
@@ -146022,7 +146399,7 @@ var init_registry2 = __esm({
146022
146399
  "../packages/manifest/schema-contract/registry.mjs"() {
146023
146400
  "use strict";
146024
146401
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
146025
- cliVersion: "0.61.3",
146402
+ cliVersion: "0.61.4",
146026
146403
  projectFileUploadBatchSize: 32,
146027
146404
  documentRecords: {
146028
146405
  member: {
@@ -154702,7 +155079,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
154702
155079
  async function main() {
154703
155080
  const args = parseArgs(process.argv.slice(2));
154704
155081
  if (args.command === "--version") {
154705
- console.log("0.61.3");
155082
+ console.log("0.61.4");
154706
155083
  return;
154707
155084
  }
154708
155085
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.61.3",
3
+ "version": "0.61.4",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.61.3 -->
12
+ <!-- reviewed-through-cli: 0.61.4 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.61.3 -->
113
+ <!-- reviewed-through-cli: 0.61.4 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -158,6 +158,11 @@ chain, so `config.Hat?.Head.Idle.Down.Frames` safely returns null when `Hat` is
158
158
  null. A descendant that is itself nullable still needs its own `?.`, `??`, `!`,
159
159
  or narrowing guard before the next access.
160
160
 
161
+ Inside an instance body, `Member` and `this.Member` share null and type-check
162
+ narrowing. A local or parameter named `Member` shadows the implicit member;
163
+ `this.Member` still names the instance field. Reassigning a narrowed path to a
164
+ nullable value clears its non-null fact.
165
+
161
166
  Treat lookup-return values with the same nullable narrowing rules.
162
167
 
163
168
  Runtime declaration patterns follow the possible runtime type, not only the