@neocompose/cli 0.89.8 → 0.89.9

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.
@@ -25397,6 +25397,7 @@ function indexRuntimeCollectionChange(collection, entry, ctx, removedEntries = n
25397
25397
  indexes4.memberByRowId.clear();
25398
25398
  }
25399
25399
  if (Array.isArray(collection)) forgetLocalListIndexes(ctx, collection);
25400
+ else indexes4.declaredListByArray = /* @__PURE__ */ new WeakMap();
25400
25401
  markOverlayMutated(ctx, collection);
25401
25402
  if (ctx.__valueOverlay instanceof LazyValueOverlay)
25402
25403
  indexes4.overlayRevision = ctx.__valueOverlay.revision;
@@ -29012,6 +29013,7 @@ function replaceRowValue(row, value, receiver, ctx) {
29012
29013
  unindexEvaluatorRow(indexes4, row, true);
29013
29014
  row.value = value;
29014
29015
  indexEvaluatorRow(indexes4, row, true);
29016
+ indexes4.declaredListByArray = /* @__PURE__ */ new WeakMap();
29015
29017
  markOverlayMutated(ctx, receiver);
29016
29018
  if (ctx.__valueOverlay instanceof LazyValueOverlay)
29017
29019
  indexes4.overlayRevision = ctx.__valueOverlay.revision;
@@ -33268,6 +33270,17 @@ function runtimeTypeCheck(value, checkType, ctx, seenCollections = /* @__PURE__
33268
33270
  case 8 /* Enum */:
33269
33271
  return Array.isArray(value) && value.every((v) => typeof v === "string");
33270
33272
  case 7 /* Class */: {
33273
+ if (isVariantClassId(checkType.classId)) {
33274
+ if (!isVariantRefValue(value)) return false;
33275
+ const targetParameter = evaluatorSchemaIndex(ctx).classesById.get(
33276
+ checkType.classId
33277
+ )?.genericParams?.[0];
33278
+ const target = targetParameter === void 0 ? void 0 : receiverClosedTypeArguments(checkType.typeArguments, ctx)?.[targetParameter.id];
33279
+ return target?.type !== 7 /* Class */ || evaluatorSchemaIndex(ctx).classIsOrExtends(
33280
+ value.classId,
33281
+ target.classId
33282
+ );
33283
+ }
33271
33284
  if (typeof value !== "object") return false;
33272
33285
  const row = trackedRowForValueReference(value, ctx);
33273
33286
  const runtimeClassId = row === null ? void 0 : classIdForValueRow(row, ctx);
@@ -38464,6 +38477,21 @@ function encodeRequiredConstructorArgument(args) {
38464
38477
  `Constructor argument '${args.parameterId}' has an unresolved generic type.`
38465
38478
  );
38466
38479
  }
38480
+ if (args.typeInfo.type === 7 /* Class */ && isVariantClassId(args.typeInfo.classId)) {
38481
+ if (args.value === null || args.value === void 0) return null;
38482
+ const selection = readVariantSelection(
38483
+ args.value,
38484
+ `Constructor argument '${args.parameterId}'`
38485
+ );
38486
+ if (selection.rowValueId !== null) {
38487
+ adoptTracked(selection.rowValueId, "variant-row");
38488
+ }
38489
+ return {
38490
+ classId: selection.classId,
38491
+ variantId: selection.variantId,
38492
+ ...selection.rowValueId === null ? {} : { rowValueId: selection.rowValueId }
38493
+ };
38494
+ }
38467
38495
  if (args.typeInfo.type === 7 /* Class */ || args.typeInfo.type === 22 /* Interface */) {
38468
38496
  if (args.value === null || args.value === void 0) return null;
38469
38497
  const aggregateId2 = trackedId ?? replayedAggregateId;
@@ -45504,22 +45532,29 @@ function lookupEntryTypeInfo2(member, collectionTypeInfo) {
45504
45532
  }
45505
45533
 
45506
45534
  // ../packages/neoscript-runtime/src/values/member-declared-type-info.ts
45507
- function resolveMemberDeclaredTypeInfo(member, members2, ownerClass = null, env) {
45535
+ function resolveMemberDeclaredTypeInfo(member, members2, classes, ownerClass = null, env) {
45508
45536
  return resolveMemberDeclaredTypeInfoInternal(
45509
45537
  member,
45510
45538
  members2,
45539
+ classes,
45511
45540
  /* @__PURE__ */ new Set(),
45512
45541
  ownerClass,
45513
45542
  env,
45514
45543
  ownerClass === null ? void 0 : classConstrainedGenerics([ownerClass])
45515
45544
  );
45516
45545
  }
45517
- function resolveMemberDeclaredTypeInfoInternal(member, members2, seen, ownerClass, env, isClassConstrained) {
45546
+ function resolveMemberDeclaredTypeInfoInternal(member, members2, classes, seen, ownerClass, env, isClassConstrained) {
45518
45547
  const resolved = env === void 0 ? resolveMember(member, members2) : substituteMember(member, env, members2);
45519
45548
  const memberId = getField(resolved, "id");
45520
45549
  if (typeof memberId === "string" && seen.has(memberId)) return null;
45521
45550
  const nextSeen = typeof memberId === "string" ? /* @__PURE__ */ new Set([...seen, memberId]) : new Set(seen);
45522
45551
  if (isMemberNSPropertyBase(resolved)) return resolved.returnTypeInfo;
45552
+ if (isMemberVariantBase(resolved)) {
45553
+ return variantMemberDeclaredTypeInfo(
45554
+ resolved,
45555
+ neoVariantSystemClass(classes, resolved.valueTypeInfo != null)
45556
+ );
45557
+ }
45523
45558
  if (isClassSlotMember(resolved)) {
45524
45559
  const typeArguments = Object.fromEntries(
45525
45560
  Object.entries(declaredSlotArguments(resolved) ?? {}).flatMap(
@@ -45539,6 +45574,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members2, seen, ownerClas
45539
45574
  const argumentType2 = resolveMemberDeclaredTypeInfoInternal(
45540
45575
  argumentMember,
45541
45576
  members2,
45577
+ classes,
45542
45578
  nextSeen,
45543
45579
  ownerClass,
45544
45580
  env,
@@ -45609,6 +45645,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members2, seen, ownerClas
45609
45645
  const typeInfo = resolveMemberDeclaredTypeInfoInternal(
45610
45646
  descriptor,
45611
45647
  members2,
45648
+ classes,
45612
45649
  nextSeen,
45613
45650
  ownerClass,
45614
45651
  env,
@@ -45637,6 +45674,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members2, seen, ownerClas
45637
45674
  const entryTypeInfo = resolveMemberDeclaredTypeInfoInternal(
45638
45675
  entry,
45639
45676
  members2,
45677
+ classes,
45640
45678
  nextSeen,
45641
45679
  ownerClass,
45642
45680
  env,
@@ -45661,6 +45699,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members2, seen, ownerClas
45661
45699
  return resolveMemberDeclaredTypeInfoInternal(
45662
45700
  collection,
45663
45701
  members2,
45702
+ classes,
45664
45703
  nextSeen,
45665
45704
  ownerClass,
45666
45705
  env,
@@ -45703,6 +45742,15 @@ function recordEntries(value) {
45703
45742
  if (typeof value !== "object" || value === null) return [];
45704
45743
  return Object.values(value);
45705
45744
  }
45745
+ function isVariantArgument(value, typeInfo) {
45746
+ return typeInfo?.type === 7 /* Class */ && isVariantClassId(typeInfo.classId) && isVariantRefValue(value);
45747
+ }
45748
+ function remapVariantArgument(value, typeInfo, remap) {
45749
+ if (!isVariantArgument(value, typeInfo)) return value;
45750
+ if (typeof value.rowValueId !== "string") return value;
45751
+ const rowValueId = remap(value.rowValueId);
45752
+ return rowValueId === value.rowValueId ? value : { ...value, rowValueId };
45753
+ }
45706
45754
  var MaterializedValueGraphContext = class _MaterializedValueGraphContext {
45707
45755
  /**
45708
45756
  * P79 §7a. Generic in the caller's document view so a pass typed against a
@@ -45939,7 +45987,7 @@ var MaterializedValueGraphContext = class _MaterializedValueGraphContext {
45939
45987
  const typeInfo = parameterTypes.get(parameterId);
45940
45988
  return [
45941
45989
  parameterId,
45942
- typeInfo !== void 0 && (isAggregateTypeInfo(typeInfo) || typeInfo.type === 29 /* Reference */) && typeof argument2 === "string" ? rewriteId(argument2) : argument2
45990
+ typeInfo !== void 0 && (isAggregateTypeInfo(typeInfo) || typeInfo.type === 29 /* Reference */) && typeof argument2 === "string" ? rewriteId(argument2) : remapVariantArgument(argument2, typeInfo, rewriteId)
45943
45991
  ];
45944
45992
  }
45945
45993
  )
@@ -45975,15 +46023,18 @@ var MaterializedValueGraphContext = class _MaterializedValueGraphContext {
45975
46023
  ids.push(edge.targetValueId);
45976
46024
  return ids;
45977
46025
  }
45978
- /** The targets a row's Reference creation data names (P112 §1.1). */
46026
+ /** Reference targets and bound lookup rows retained by creation data. */
45979
46027
  referenceArgumentIds(row) {
45980
46028
  if (!isLiteralValueContent(row)) return [];
45981
46029
  const args = row.constructorArgs;
45982
46030
  if (args === void 0 || args === null) return [];
45983
46031
  const ids = [];
45984
- for (const parameterId of this.referenceParameterIds(row)) {
46032
+ for (const [parameterId, typeInfo] of this.constructorParameterTypes(row)) {
45985
46033
  const argument2 = args[parameterId];
45986
- if (typeof argument2 === "string") ids.push(argument2);
46034
+ if (typeInfo.type === 29 /* Reference */ && typeof argument2 === "string")
46035
+ ids.push(argument2);
46036
+ else if (isVariantArgument(argument2, typeInfo) && typeof argument2.rowValueId === "string")
46037
+ ids.push(argument2.rowValueId);
45987
46038
  }
45988
46039
  return ids;
45989
46040
  }
@@ -45992,23 +46043,23 @@ var MaterializedValueGraphContext = class _MaterializedValueGraphContext {
45992
46043
  const args = row.constructorArgs;
45993
46044
  if (args === void 0 || args === null) return args;
45994
46045
  let remapped;
45995
- for (const parameterId of this.referenceParameterIds(
46046
+ for (const [parameterId, typeInfo] of this.constructorParameterTypes(
45996
46047
  row,
45997
46048
  contextualMember
45998
46049
  )) {
45999
46050
  const argument2 = args[parameterId];
46000
- if (typeof argument2 !== "string") continue;
46001
- const target = remap(argument2);
46051
+ const target = typeInfo.type === 29 /* Reference */ && typeof argument2 === "string" ? remap(argument2) : remapVariantArgument(argument2, typeInfo, remap);
46002
46052
  if (target !== argument2) (remapped ??= { ...args })[parameterId] = target;
46003
46053
  }
46004
46054
  return remapped ?? args;
46005
46055
  }
46006
- *referenceParameterIds(row, contextualMember) {
46007
- for (const [parameterId, typeInfo] of this.constructorParameterTypes(
46008
- row,
46009
- contextualMember
46010
- ))
46011
- if (typeInfo.type === 29 /* Reference */) yield parameterId;
46056
+ *variantArgumentSelections(row) {
46057
+ if (!isLiteralValueContent(row)) return;
46058
+ if (row.constructorArgs == null) return;
46059
+ for (const [parameterId, typeInfo] of this.constructorParameterTypes(row)) {
46060
+ const argument2 = row.constructorArgs[parameterId];
46061
+ if (isVariantArgument(argument2, typeInfo)) yield argument2;
46062
+ }
46012
46063
  }
46013
46064
  slotEntries(row) {
46014
46065
  const value = row.value;
@@ -46109,6 +46160,17 @@ var MaterializedValueGraphContext = class _MaterializedValueGraphContext {
46109
46160
  if (member === void 0) return null;
46110
46161
  return this.memberTypeInfo(member, env);
46111
46162
  }
46163
+ if ((rawTypeInfo.type === 7 /* Class */ || rawTypeInfo.type === 22 /* Interface */) && rawTypeInfo.typeArguments !== void 0) {
46164
+ const typeArguments = {};
46165
+ for (const [parameterId, argument2] of Object.entries(
46166
+ rawTypeInfo.typeArguments
46167
+ )) {
46168
+ const resolved = this.resolveTypeInfo(argument2, env);
46169
+ if (resolved === null) return null;
46170
+ typeArguments[parameterId] = resolved;
46171
+ }
46172
+ return { ...rawTypeInfo, typeArguments };
46173
+ }
46112
46174
  if (rawTypeInfo.type === 30 /* Tuple */) {
46113
46175
  const elements = [];
46114
46176
  for (const element of rawTypeInfo.elements) {
@@ -46140,7 +46202,8 @@ var MaterializedValueGraphContext = class _MaterializedValueGraphContext {
46140
46202
  const member = substituteMember(rawMember, env, this.document.members);
46141
46203
  const typeInfo = resolveMemberDeclaredTypeInfo(
46142
46204
  member,
46143
- this.document.members
46205
+ this.document.members,
46206
+ this.document.classes
46144
46207
  );
46145
46208
  return typeInfo === null ? null : this.resolveTypeInfo(typeInfo, env);
46146
46209
  } catch {
@@ -46377,7 +46440,11 @@ function tryResolveTypeInfo(document2, rawTypeInfo, env) {
46377
46440
  function tryMemberTypeInfo(document2, rawMember, env) {
46378
46441
  try {
46379
46442
  const member = substituteMember(rawMember, env, document2.members);
46380
- const typeInfo = resolveMemberDeclaredTypeInfo(member, document2.members);
46443
+ const typeInfo = resolveMemberDeclaredTypeInfo(
46444
+ member,
46445
+ document2.members,
46446
+ document2.classes
46447
+ );
46381
46448
  if (typeInfo === null) return null;
46382
46449
  return tryResolveTypeInfo(document2, typeInfo, env);
46383
46450
  } catch {
@@ -58921,11 +58988,97 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
58921
58988
  if (callee.kind !== "member") return null;
58922
58989
  const isInitialize = callee.name === VARIANT_INITIALIZE_NAME;
58923
58990
  const isApply = callee.name === VARIANT_APPLY_NAME;
58924
- if (!isInitialize && !isApply) return null;
58991
+ const isBind = callee.name === "Bind";
58992
+ if (!isInitialize && !isApply && !isBind) return null;
58925
58993
  if (callee.receiver.kind === "ident" && !this.hasValueReceiver(callee.receiver.name, scope) && this.project.typeByName.has(callee.receiver.name)) {
58926
58994
  return null;
58927
58995
  }
58928
58996
  const receiver = receiverValue ? receiverValue() : this.resolveExpression(callee.receiver, scope);
58997
+ if (isBind) {
58998
+ const variantInfo2 = this.variantTypeInfo(receiver.type);
58999
+ if (variantInfo2 === null) return null;
59000
+ if (variantInfo2.valueType === null) {
59001
+ throw new CompileError("Only a lookup variant can bind a row.", pos);
59002
+ }
59003
+ if (callee.optional === true) {
59004
+ throw new CompileError("Bind requires a non-null lookup variant.", pos);
59005
+ }
59006
+ if (receiver.pointer.type !== "variant" /* Variant */) {
59007
+ throw new CompileError(
59008
+ "Bind requires a declared lookup variant path.",
59009
+ pos
59010
+ );
59011
+ }
59012
+ requireArgCount("NeoLookupVariant.Bind", argumentsList2, 1, pos);
59013
+ const row2 = argumentsList2[0];
59014
+ if (row2.kind !== "call") {
59015
+ throw new CompileError(
59016
+ 'Bind requires Reference(id: "value-id").',
59017
+ row2.pos
59018
+ );
59019
+ }
59020
+ if (row2.callee.kind !== "ident" || row2.callee.name !== "Reference") {
59021
+ throw new CompileError(
59022
+ "Bind requires a Reference row argument.",
59023
+ row2.pos
59024
+ );
59025
+ }
59026
+ requireArgCount("Bind Reference", row2.args, 1, row2.pos);
59027
+ if (row2.argumentNames?.[0] !== "id") {
59028
+ throw new CompileError(
59029
+ 'Bind requires Reference(id: "value-id").',
59030
+ row2.pos
59031
+ );
59032
+ }
59033
+ const id = row2.args[0];
59034
+ if (id.kind !== "litString") {
59035
+ throw new CompileError(
59036
+ "Bound row id must be a string literal.",
59037
+ id.pos
59038
+ );
59039
+ }
59040
+ if (id.value.length === 0) {
59041
+ throw new CompileError("Bound row id must not be empty.", id.pos);
59042
+ }
59043
+ if (row2.typeArguments !== void 0) {
59044
+ if (row2.typeArguments.length !== 1) {
59045
+ throw new CompileError(
59046
+ "Reference requires exactly one generic target type.",
59047
+ row2.pos
59048
+ );
59049
+ }
59050
+ this.requireAssignable(
59051
+ this.resolveType(row2.typeArguments[0]),
59052
+ variantInfo2.valueType,
59053
+ row2.pos,
59054
+ "Lookup variant row"
59055
+ );
59056
+ }
59057
+ const rowType = this.context.persistedReferenceTypes?.get(id.value);
59058
+ if (rowType !== void 0) {
59059
+ this.requireAssignable(
59060
+ neoScriptReadType(rowType),
59061
+ variantInfo2.valueType,
59062
+ row2.pos,
59063
+ "Lookup variant row"
59064
+ );
59065
+ }
59066
+ const bound = this.project.typeByName.get(NEO_VARIANT_TYPE_NAME);
59067
+ if (bound === void 0) {
59068
+ throw new CompileError(
59069
+ `The seeded '${NEO_VARIANT_TYPE_NAME}' class is missing.`,
59070
+ pos
59071
+ );
59072
+ }
59073
+ return {
59074
+ pointer: { ...receiver.pointer, rowValueId: id.value },
59075
+ type: {
59076
+ kind: "named",
59077
+ typeId: bound.id,
59078
+ typeArguments: receiver.type.kind === "named" ? receiver.type.typeArguments?.slice(0, 1) ?? [] : []
59079
+ }
59080
+ };
59081
+ }
58929
59082
  if (isInitialize) {
58930
59083
  const variantInfo2 = this.variantTypeInfo(receiver.type);
58931
59084
  if (variantInfo2 === null) return null;
@@ -60827,6 +60980,33 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
60827
60980
  typeArguments
60828
60981
  );
60829
60982
  }
60983
+ if (callee.kind === "lambda" && expected !== void 0) {
60984
+ rejectTypeArguments();
60985
+ const parameterTypes = callee.params.map((parameter3) => {
60986
+ if (parameter3.type === null) {
60987
+ throw new CompileError(
60988
+ "An immediately invoked lambda requires explicit parameter types.",
60989
+ parameter3.pos
60990
+ );
60991
+ }
60992
+ return this.resolveType(parameter3.type);
60993
+ });
60994
+ return this.resolveDelegateCall(
60995
+ this.resolveDelegateLambda(
60996
+ callee,
60997
+ {
60998
+ kind: "delegate",
60999
+ parameterTypes,
61000
+ returnType: expected
61001
+ },
61002
+ scope
61003
+ ),
61004
+ argumentsList2,
61005
+ scope,
61006
+ pos,
61007
+ false
61008
+ );
61009
+ }
60830
61010
  const variantCall = this.resolveVariantCall(
60831
61011
  callee,
60832
61012
  argumentsList2,
@@ -71527,7 +71707,7 @@ function resolveAnalyzerMember(context, member) {
71527
71707
  }
71528
71708
 
71529
71709
  // ../src/runtime/neoscript/neoscript-language-context-adapter.ts
71530
- var NEOSCRIPT_COMPILER_ADAPTER_REVISION = 6;
71710
+ var NEOSCRIPT_COMPILER_ADAPTER_REVISION = 7;
71531
71711
  var UNKNOWN_TYPE = {
71532
71712
  kind: "primitive",
71533
71713
  name: "unknown"
@@ -73817,6 +73997,7 @@ function compileMemberInitializerBody(args) {
73817
73997
  const returnTypeInfo = resolveMemberDeclaredTypeInfo(
73818
73998
  args.member,
73819
73999
  members2,
74000
+ args.classes,
73820
74001
  args.thisClass
73821
74002
  );
73822
74003
  if (returnTypeInfo === null) {
@@ -73864,7 +74045,8 @@ function compileValueRowInitializerBody(args) {
73864
74045
  }
73865
74046
  const returnTypeInfo = resolveMemberDeclaredTypeInfo(
73866
74047
  args.member,
73867
- args.members
74048
+ args.members,
74049
+ args.classes
73868
74050
  );
73869
74051
  if (returnTypeInfo === null) {
73870
74052
  throw new Error(
@@ -73997,7 +74179,9 @@ function compilationProjectForDocument(document2, state) {
73997
74179
  classes: document2.classes,
73998
74180
  enums: document2.enums,
73999
74181
  interfaces: document2.interfaces,
74000
- constructors: document2.constructors ?? []
74182
+ constructors: document2.constructors ?? [],
74183
+ variants: document2.variants ?? [],
74184
+ variantFolders: document2.variantFolders ?? []
74001
74185
  });
74002
74186
  return state.compilationProject;
74003
74187
  }
@@ -74093,6 +74277,8 @@ function compileValueInitializerDependency(document2, valueId, compilationProjec
74093
74277
  enums: document2.enums,
74094
74278
  interfaces: document2.interfaces,
74095
74279
  constructors: document2.constructors ?? [],
74280
+ variants: document2.variants ?? [],
74281
+ variantFolders: document2.variantFolders ?? [],
74096
74282
  member: site.member,
74097
74283
  valueRow: { ...site.row },
74098
74284
  valueId: site.row.id,
@@ -74111,7 +74297,9 @@ function compileMemberInitializerDependency(document2, memberId, compilationProj
74111
74297
  classes: document2.classes,
74112
74298
  enums: document2.enums,
74113
74299
  interfaces: document2.interfaces,
74114
- constructors: document2.constructors ?? []
74300
+ constructors: document2.constructors ?? [],
74301
+ variants: document2.variants ?? [],
74302
+ variantFolders: document2.variantFolders ?? []
74115
74303
  };
74116
74304
  const memberIndex = documentMemberCompilationIndex(document2);
74117
74305
  const member = memberIndex.membersById.get(memberId);
@@ -86384,7 +86572,9 @@ function buildNeoScriptProject(document2) {
86384
86572
  classes: document2.classes,
86385
86573
  enums: document2.enums,
86386
86574
  interfaces: document2.interfaces,
86387
- constructors: document2.constructors ?? []
86575
+ constructors: document2.constructors ?? [],
86576
+ variants: document2.variants,
86577
+ variantFolders: document2.variantFolders
86388
86578
  });
86389
86579
  }
86390
86580
  function neoScriptProjectFingerprint(document2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.89.8",
3
+ "version": "0.89.9",
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.89.8 -->
12
+ <!-- reviewed-through-cli: 0.89.9 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -143,7 +143,7 @@ The marker near the top of `SKILL.md` must exactly match the package version,
143
143
  and this quoted copy is checked too:
144
144
 
145
145
  ```html
146
- <!-- reviewed-through-cli: 0.89.8 -->
146
+ <!-- reviewed-through-cli: 0.89.9 -->
147
147
  ```
148
148
 
149
149
  Keep this skill on current behavior. Upgrade steps, rollout prerequisites,
@@ -31,6 +31,26 @@ Foo value = new(bar: "BAR", isFun: true) { IsFun = false };
31
31
  - A header constructor disables implicit parameterless `new` and forbids
32
32
  other constructor overloads. Its derived identity takes no `@id`.
33
33
 
34
+ ## Variant parameters
35
+
36
+ Constructors accept `NeoVariant<T>` and `NeoLookupVariant<T, TValue>` selections,
37
+ including nullable selections and generic targets such as `NeoVariant<Kid<int>>`.
38
+ A member initializer can retain the selection for each instance:
39
+
40
+ ```neo
41
+ class Box : NeoObject {}
42
+ class Made(NeoVariant<Box> selection) {
43
+ public NeoVariant<Box> Selection = selection;
44
+ }
45
+ Made value = new(Box.Variants.Base);
46
+ ```
47
+
48
+ Push and export retain the selected variant and any bound lookup row for replay.
49
+ `null` is an empty nullable selection; `Box.Variants.Base` selects the base class.
50
+ To pass a bound lookup selection directly, use a declared variant path and a
51
+ stored row ID, such as `new Made(Box.Variants.Seeds.BySeed.Bind(Reference(id: "row-id")))`.
52
+ This compiles to a fixed selection; `Bind` does not evaluate a dynamic row expression.
53
+
34
54
  ## Declared constructors
35
55
 
36
56
  Declare overloadable constructors as members when each body owns its own
@@ -39,6 +39,9 @@ abstract class Item<TContext extends SomeClass> : INamed {
39
39
  The `@id`s above are pulled IDs. New declarations omit them; push assigns them.
40
40
  Never put `@id` on inferred list/dictionary entry types or derived structural
41
41
  descriptors.
42
+ Generic bindings and their nested collection descriptors follow the owning
43
+ declaration's identity. Deleting and recreating an idless declaration creates
44
+ fresh bindings; retaining its pulled IDs preserves them.
42
45
 
43
46
  ## Modifiers
44
47
 
@@ -50,6 +50,8 @@ return this.Beds
50
50
  entries. Declared index lookups (`items.Index.Name[key]`) preserve the source
51
51
  entries' write permissions: writable fields can be updated through unique
52
52
  results or a bucket's entries, while bucket membership stays read-only.
53
+ Subsequent index lookups reflect writes to indexed fields, including writes
54
+ through `Where` results.
53
55
 
54
56
  ## Reductions
55
57
 
@@ -18,6 +18,8 @@ List<Item> ready = this.Items.Where((item) => item.Count > 0);
18
18
  - Call any non-null delegate expression directly: `callbacks[0](value)`,
19
19
  `(cond ? a : b)()`, `Factory.Make()()`. Force-unwrap a nullable one first:
20
20
  `callbacks["key"]!()`. Void delegates are statements only.
21
+ - A typed lambda can be called immediately when its result has an expected
22
+ type: `int n = ((int x) => x + 1)(2);`. Declare each parameter's type.
21
23
  - Inside a class body, invoke delegate and action members directly:
22
24
  `Selector()`, `OnUpdated()`.
23
25
 
@@ -70,7 +70,6 @@ stored instance rather than building a copy. Its owning placement keeps it,
70
70
  so a constructor that stores it needs a `Reference` field:
71
71
 
72
72
  ```neo
73
- @id("86ac7c7d-8d3b-4642-9def-cdf32643bdc5")
74
73
  DeliveryQuest HARD_AS_STONE = new();
75
74
  // The core quest list owns HARD_AS_STONE.
76
75
  WorkflowStep UNLOCK = new QuestUnlockStep(HARD_AS_STONE);
@@ -82,13 +81,15 @@ class QuestUnlockStep(DeliveryQuest quest) : WorkflowStep {
82
81
 
83
82
  - The local needs exactly one owning placement. Unowned locals, ambiguous
84
83
  placements, and cycles are source errors.
85
- - A nested target needs its existing `@id`. A structurally owned list entry
86
- may use an unused UUID-v4 in the same push.
87
- - A local nested inside an evaluated constructor stays idless on first push;
88
- push assigns its identity before it can be referenced. Don't give it a fresh
89
- UUID.
90
- - A direct static owner supplies a pending identity for an idless local, and
91
- aliases of that static local work too.
84
+ - New nested locals can be referenced before their first push or export, including
85
+ inside lists, dictionaries, and constructor-backed owners. Preparation resolves
86
+ each reference to the row its unique owner creates. Aliases share that identity.
87
+ Sibling constructor arguments and explicit `Reference(LOCAL)` calls reuse the
88
+ child within the same owning tree. A parent constructor may borrow a child
89
+ that one of its field overrides owns; pull preserves both uses.
90
+ Direct `Reference(LOCAL)` fields resolve after the owning constructor runs.
91
+ - Push writes assigned IDs back to source. Dry-run and export prepare the same
92
+ references without requiring hand-written IDs or an earlier push.
92
93
  - A member default is a construction template, so its local is not a single
93
94
  stored instance.
94
95
  - Owned locals inside evaluated initializers need directly addressable fields,
@@ -44,6 +44,8 @@ frame. For editing an existing clip, see [frame overrides](animation.md).
44
44
  - Selections compare equal when class, variant, and bound lookup row match.
45
45
  An unset optional selection differs from `Base`.
46
46
 
47
+ Variant selections can also be [constructor parameters](../declarations/constructors.md#variant-parameters). A constructor can retain a named, base, or bound lookup selection in a member initializer.
48
+
47
49
  ## Folders
48
50
 
49
51
  ```neo
@@ -112,8 +114,9 @@ NeoLookupVariant<ExampleObject, NeoSortingLayer> Layered = new(
112
114
  ## Type restrictions
113
115
 
114
116
  `NeoVariantFolder` and `NeoLookupVariantFolder` are top-level records, never
115
- member or parameter types. Variant handles are valid as Variant members only,
116
- not as collection entries, lookup targets, parameters, or generic bindings.
117
+ member or parameter types. Variant handles are valid as Variant members and
118
+ constructor parameters, not as collection entries, lookup targets, ordinary
119
+ function parameters, or generic bindings.
117
120
 
118
121
  ## Stored overrides
119
122