@neocompose/cli 0.52.3 → 0.52.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.52.4] - 2026-09-17
4
+
5
+ - Accept declared list-index lookups in constructor arguments, including readable string keys such as `root.Assets.Items.Index.Slug["stone"]!`.
6
+ - Convert session-constructed tiles with either `TryConvert` overload, preserving their identity and Cell, and persist their final class in migrations.
7
+ - Allow object initializers in executable NeoScript and specs. Construct first, then apply entries in order using ordinary assignment rules, including property setters and readonly checks.
8
+ - Encode constructor-assigned collections as value rows when exporting initializers so seeding an Assets list preserves existing NeoCellPattern values. List-shape errors name the list kind.
9
+
3
10
  ## [0.52.3] - 2026-09-17
4
11
 
5
12
  - Replay stored collection constructor arguments as typed literals, including evaluated NeoCellPattern values.
package/dist/neo.mjs CHANGED
@@ -6401,6 +6401,7 @@ var init_strict_resolver = __esm({
6401
6401
  callSiteCounter = 0;
6402
6402
  lambdaDepth = 0;
6403
6403
  delegateClosureCounter = 0;
6404
+ objectInitializerCounter = 0;
6404
6405
  delegateCaptureContexts = [];
6405
6406
  sourceTokens;
6406
6407
  source;
@@ -8013,6 +8014,14 @@ var init_strict_resolver = __esm({
8013
8014
  expression.pos
8014
8015
  );
8015
8016
  const declaredTarget = this.project.typeByName.get(className);
8017
+ if (declaredTarget?.kind === "class" && (expression.initializer?.length ?? 0) > 0 && this.context.storedConstructionReplay !== true) {
8018
+ return this.resolveExecutableObjectInitializer(
8019
+ declaredTarget,
8020
+ expression,
8021
+ scope,
8022
+ expected
8023
+ );
8024
+ }
8016
8025
  const declared = declaredTarget?.declaredConstructors ?? [];
8017
8026
  const requiredConstructor = neoScriptRequiredConstructor(declaredTarget);
8018
8027
  const generatedPositionalCall = requiredConstructor === null && declaredTarget?.constructorSignature !== void 0 && expression.argumentNames === void 0 && callHasPositionalArguments(expression);
@@ -8031,12 +8040,6 @@ var init_strict_resolver = __esm({
8031
8040
  expression.pos
8032
8041
  );
8033
8042
  }
8034
- if (expression.initializer && expression.initializer.length > 0 && !generatedPositionalCall && this.context.storedConstructionReplay !== true) {
8035
- throw new CompileError(
8036
- "Object initializers are available to project source construction and are not executable NeoScript constructors.",
8037
- expression.pos
8038
- );
8039
- }
8040
8043
  return this.resolveClassConstructor(
8041
8044
  className,
8042
8045
  expression,
@@ -10263,6 +10266,75 @@ var init_strict_resolver = __esm({
10263
10266
  type: optional2 ? { ...delegateType.returnType, nullable: true } : delegateType.returnType
10264
10267
  };
10265
10268
  }
10269
+ resolveExecutableObjectInitializer(schemaClass2, expression, scope, expected) {
10270
+ const constructed = this.resolveExpression(
10271
+ { ...expression, className: schemaClass2.name, initializer: [] },
10272
+ scope,
10273
+ expected
10274
+ );
10275
+ const name = `$initialized_${this.objectInitializerCounter++}`;
10276
+ const receiver = variable(
10277
+ name,
10278
+ constructed.type,
10279
+ constructed.pointer,
10280
+ this.project
10281
+ );
10282
+ const initializerScope = new Scope(scope);
10283
+ initializerScope.define(
10284
+ scopeVariable(
10285
+ name,
10286
+ receiver,
10287
+ constructed.type,
10288
+ "local" /* Local */,
10289
+ "session" /* Session */,
10290
+ "local"
10291
+ )
10292
+ );
10293
+ const assignments = [];
10294
+ const seen = /* @__PURE__ */ new Set();
10295
+ for (const entry of expression.initializer ?? []) {
10296
+ const key = entry.name.toLowerCase();
10297
+ if (seen.has(key)) {
10298
+ throw new CompileError(
10299
+ `Object initializer sets '${entry.name}' more than once.`,
10300
+ entry.pos
10301
+ );
10302
+ }
10303
+ seen.add(key);
10304
+ const assignment = this.resolveAssignment(
10305
+ {
10306
+ kind: "assign",
10307
+ op: "=",
10308
+ pos: entry.pos,
10309
+ value: entry.value,
10310
+ target: {
10311
+ kind: "member",
10312
+ name: entry.name,
10313
+ pos: entry.pos,
10314
+ receiver: { kind: "ident", name, pos: entry.pos }
10315
+ }
10316
+ },
10317
+ initializerScope
10318
+ );
10319
+ if (assignment.type !== "assign" /* Assign */) {
10320
+ throw new CompileError(
10321
+ "Object initializer entries must be assignments.",
10322
+ entry.pos
10323
+ );
10324
+ }
10325
+ assignments.push(assignment);
10326
+ }
10327
+ return {
10328
+ pointer: {
10329
+ type: "objectInitializer" /* ObjectInitializer */,
10330
+ receiver,
10331
+ assignments
10332
+ },
10333
+ type: constructed.type,
10334
+ writability: "session" /* Session */,
10335
+ writeRoot: "local"
10336
+ };
10337
+ }
10266
10338
  resolveClassConstructor(className, expression, scope, pos, expected) {
10267
10339
  const schemaClass2 = this.project.typeByName.get(className);
10268
10340
  if (!schemaClass2 || schemaClass2.kind !== "class") {
@@ -12371,11 +12443,7 @@ var init_strict_resolver = __esm({
12371
12443
  */
12372
12444
  localWriteTargetRoot(initial, scope) {
12373
12445
  if (this.context.kind !== "constructor") return "local";
12374
- if (initial.pointer.type === "function" /* Function */) {
12375
- return CONSTRUCTING_FUNCTION_KINDS.has(initial.pointer.function.type) ? "local" : "unknown";
12376
- }
12377
- if (initial.pointer.type === "value" /* Value */) return "local";
12378
- return this.writeThroughRoot(initial.pointer, scope);
12446
+ return this.expressionWriteRoot(initial, scope);
12379
12447
  }
12380
12448
  validateCallableInContext(symbol, pos) {
12381
12449
  if (symbol.native === true && !NEO_CELL_PATTERN_PURE_NATIVE_MEMBER_IDS.has(symbol.id) && isNeoScriptConstructionDocumentKind(this.context.kind)) {
@@ -22364,7 +22432,7 @@ function validateDeclaration(uri, declaration, documentKind, environment, diagno
22364
22432
  }
22365
22433
  }
22366
22434
  for (const member of declaration.members) {
22367
- scope.set(member.name, semanticType(member.type));
22435
+ scope.set(member.name, semanticMemberType(member, environment));
22368
22436
  }
22369
22437
  for (const member of declaration.members) {
22370
22438
  validateMember(
@@ -23748,7 +23816,7 @@ function validateExpression(expression, expected, scope, environment, uri, range
23748
23816
  if (expression.kind === "unary" || expression.kind === "force") {
23749
23817
  validateExpression(
23750
23818
  expression.operand,
23751
- expected,
23819
+ expression.kind === "force" && expected ? { ...expected, nullable: true } : expected,
23752
23820
  scope,
23753
23821
  environment,
23754
23822
  uri,
@@ -23780,7 +23848,9 @@ function validateExpression(expression, expected, scope, environment, uri, range
23780
23848
  );
23781
23849
  validateExpression(
23782
23850
  expression.index,
23783
- primitiveType("int"),
23851
+ subscriptKeyType(
23852
+ inferExpressionType2(expression.receiver, scope, environment)
23853
+ ),
23784
23854
  scope,
23785
23855
  environment,
23786
23856
  uri,
@@ -23867,10 +23937,10 @@ function anchorPosition(anchor, offset) {
23867
23937
  }
23868
23938
  return { line: anchor.start.line + line, character };
23869
23939
  }
23870
- function sourceClassFieldByName(declaration, name, environment) {
23940
+ function sourceClassFieldByName(declaration, name, environment, initialBindings = /* @__PURE__ */ new Map()) {
23871
23941
  const visited = /* @__PURE__ */ new Set();
23872
23942
  let current = declaration;
23873
- let bindings = /* @__PURE__ */ new Map();
23943
+ let bindings = initialBindings;
23874
23944
  while (current !== void 0 && !visited.has(current.name)) {
23875
23945
  visited.add(current.name);
23876
23946
  const member = current.members.find(
@@ -24013,6 +24083,21 @@ function inferExpressionType2(expression, scope, environment) {
24013
24083
  return primitiveType("string");
24014
24084
  if (expression.kind === "new")
24015
24085
  return expression.className ? { name: expression.className } : void 0;
24086
+ if (expression.kind === "force") {
24087
+ const operand = inferExpressionType2(expression.operand, scope, environment);
24088
+ return operand ? { ...operand, nullable: false } : void 0;
24089
+ }
24090
+ if (expression.kind === "index") {
24091
+ const receiver = inferExpressionType2(
24092
+ expression.receiver,
24093
+ scope,
24094
+ environment
24095
+ );
24096
+ if (receiver?.name !== "Dictionary" && receiver?.name !== "List")
24097
+ return void 0;
24098
+ const value = receiver.arguments?.[receiver.name === "Dictionary" ? 1 : 0];
24099
+ return expression.optional && value ? { ...value, nullable: true } : value;
24100
+ }
24016
24101
  if (expression.kind === "conditional") {
24017
24102
  const whenTrue = inferExpressionType2(
24018
24103
  expression.whenTrue,
@@ -24030,15 +24115,48 @@ function inferExpressionType2(expression, scope, environment) {
24030
24115
  if (expression.receiver.kind === "ident" && environment.enums.has(expression.receiver.name)) {
24031
24116
  return { name: expression.receiver.name };
24032
24117
  }
24033
- const receiver = inferExpressionType2(
24118
+ const receiverType = inferExpressionType2(
24034
24119
  expression.receiver,
24035
24120
  scope,
24036
24121
  environment
24037
24122
  );
24123
+ const staticDeclaration = receiverType === void 0 && expression.receiver.kind === "ident" ? environment.types.get(expression.receiver.name) : void 0;
24124
+ const receiver = receiverType ?? (staticDeclaration ? { name: staticDeclaration.name } : void 0);
24125
+ if (receiver?.name === "Root") {
24126
+ const slot = PROJECT_ROOT_SOURCE_SLOTS.find(
24127
+ (slot2) => slot2.name === expression.name
24128
+ );
24129
+ return slot ? { name: slot.type } : void 0;
24130
+ }
24131
+ if (receiver?.name === "List" && receiver.listIndexes && expression.name === "Index") {
24132
+ return { name: "Index", listIndexes: receiver.listIndexes };
24133
+ }
24134
+ if (receiver?.name === "Index" && receiver.listIndexes)
24135
+ return receiver.listIndexes.get(expression.name);
24038
24136
  const declaration = receiver ? environment.types.get(receiver.name) : void 0;
24039
- const member = declaration?.members.find(
24137
+ if (!declaration) return void 0;
24138
+ const bindings = new Map(
24139
+ declaration.genericParameters.flatMap((parameter4, index) => {
24140
+ const argument2 = receiver?.arguments?.[index];
24141
+ return argument2 ? [[parameter4.name, argument2]] : [];
24142
+ })
24143
+ );
24144
+ const field = sourceClassFieldByName(
24145
+ declaration,
24146
+ expression.name,
24147
+ environment,
24148
+ bindings
24149
+ );
24150
+ if (field) {
24151
+ if (staticDeclaration && !field.member.modifiers.includes("static"))
24152
+ return void 0;
24153
+ return semanticMemberType(field.member, environment, field.bindings);
24154
+ }
24155
+ const member = declaration.members.find(
24040
24156
  (candidate) => candidate.name === expression.name
24041
24157
  );
24158
+ if (staticDeclaration && !member?.modifiers.includes("static"))
24159
+ return void 0;
24042
24160
  return member ? semanticType(member.type) : void 0;
24043
24161
  }
24044
24162
  if (expression.kind === "call" && expression.callee.kind === "ident") {
@@ -24052,6 +24170,34 @@ function inferExpressionType2(expression, scope, environment) {
24052
24170
  }
24053
24171
  return void 0;
24054
24172
  }
24173
+ function semanticMemberType(member, environment, bindings = /* @__PURE__ */ new Map()) {
24174
+ const type = substituteSemanticType(semanticType(member.type), bindings);
24175
+ if (type.name !== "List") return type;
24176
+ const entryType = type.arguments?.[0];
24177
+ if (!entryType) return type;
24178
+ const entryClass = environment.types.get(entryType.name);
24179
+ if (!entryClass) return type;
24180
+ const indexes2 = /* @__PURE__ */ new Map();
24181
+ for (const annotation2 of member.annotations) {
24182
+ if (annotation2.name !== "index") continue;
24183
+ const key = memberKeyArgument(annotation2)?.key;
24184
+ if (!key) continue;
24185
+ const field = resolveEntryField(entryClass, key, environment);
24186
+ if (!field) continue;
24187
+ const unique = contextualAnnotationEnum([annotation2], "index", "kind") === "Unique";
24188
+ const valueType = unique ? { ...entryType, nullable: true } : { name: "List", arguments: [{ ...entryType, nullable: false }] };
24189
+ indexes2.set(key, {
24190
+ name: "Dictionary",
24191
+ arguments: [semanticType(field.type), valueType]
24192
+ });
24193
+ }
24194
+ return indexes2.size === 0 ? type : { ...type, listIndexes: indexes2 };
24195
+ }
24196
+ function subscriptKeyType(receiver) {
24197
+ if (receiver?.name === "Dictionary") return receiver.arguments?.[0];
24198
+ if (receiver?.name === "List") return primitiveType("int");
24199
+ return void 0;
24200
+ }
24055
24201
  function validateReferenceExpression(expression, expected, environment, uri, range2, diagnostics) {
24056
24202
  const typeArguments = expression.typeArguments ?? [];
24057
24203
  const names = expression.argumentNames ?? [];
@@ -45932,6 +46078,13 @@ function isNSPointerCallFunction(value) {
45932
46078
  }
45933
46079
  return true;
45934
46080
  }
46081
+ function isNSPointerObjectInitializer(value) {
46082
+ if (typeof value !== "object" || value === null) return false;
46083
+ if (!("type" in value) || value.type !== "objectInitializer" /* objectInitializer */)
46084
+ return false;
46085
+ if (!("receiver" in value) || !isNSVariable(value.receiver)) return false;
46086
+ return "assignments" in value && Array.isArray(value.assignments) && value.assignments.every(isNSInstructionAssign);
46087
+ }
45935
46088
  function isNSPointerCallDelegate(value) {
45936
46089
  const v = value;
45937
46090
  if (v?.type !== "callDelegate" /* callDelegate */) return false;
@@ -45970,7 +46123,7 @@ function isNSPointerFunctionErrorCheck(value) {
45970
46123
  return isNSFunctionErrorCheckMode(v.mode);
45971
46124
  }
45972
46125
  function isNSPointer(value) {
45973
- return isNSPointerReference(value) || isNSPointerVariable(value) || isNSPointerStaticMember(value) || isNSPointerValue(value) || isNSPointerOperation(value) || isNSPointerFunction(value) || isNSPointerKeyOf(value) || isNSPointerListLiteral(value) || isNSPointerDictLiteral(value) || isNSPointerForceUnwrap(value) || isNSPointerIsCheck(value) || isNSPointerCallGetter(value) || isNSPointerCoalesce(value) || isNSPointerConditional(value) || isNSPointerDelegateClosure(value) || isNSPointerToBool(value) || isNSPointerStringify(value) || isNSPointerTileConvert(value) || isNSPointerCallFunction(value) || isNSPointerCallDelegate(value) || isNSPointerCallAction(value) || isNSPointerVariant(value) || isNSPointerFunctionErrorCheck(value);
46126
+ return isNSPointerReference(value) || isNSPointerVariable(value) || isNSPointerStaticMember(value) || isNSPointerValue(value) || isNSPointerOperation(value) || isNSPointerFunction(value) || isNSPointerKeyOf(value) || isNSPointerListLiteral(value) || isNSPointerDictLiteral(value) || isNSPointerForceUnwrap(value) || isNSPointerIsCheck(value) || isNSPointerCallGetter(value) || isNSPointerCoalesce(value) || isNSPointerConditional(value) || isNSPointerDelegateClosure(value) || isNSPointerObjectInitializer(value) || isNSPointerToBool(value) || isNSPointerStringify(value) || isNSPointerTileConvert(value) || isNSPointerCallFunction(value) || isNSPointerCallDelegate(value) || isNSPointerCallAction(value) || isNSPointerVariant(value) || isNSPointerFunctionErrorCheck(value);
45974
46127
  }
45975
46128
  function isNSPointers(value) {
45976
46129
  return Array.isArray(value) && value.every(isNSPointer);
@@ -71060,10 +71213,98 @@ function createdSessionValuesSince(ctx, checkpoint) {
71060
71213
  }
71061
71214
  return created;
71062
71215
  }
71216
+ function exportInitializerCollections(created, writes, ctx) {
71217
+ const assignedValues = /* @__PURE__ */ new Set();
71218
+ for (const write of writes) {
71219
+ if (write.kind === "assign" && write.valueId === void 0 && typeof write.value === "object" && write.value !== null) {
71220
+ assignedValues.add(write.value);
71221
+ }
71222
+ }
71223
+ const candidates = /* @__PURE__ */ new Set();
71224
+ for (const row of created) {
71225
+ if (assignedValues.has(row.value) || Array.isArray(row.value) && row.value.length > 0 && ctx.__unorderedListProjections?.byContainerId.has(row.id)) {
71226
+ candidates.add(row.id);
71227
+ }
71228
+ }
71229
+ if (candidates.size === 0) return created;
71230
+ invalidateEvaluatorIndexes(ctx, null);
71231
+ const extra = [];
71232
+ const exported = created.map((row) => {
71233
+ if (!candidates.has(row.id)) return row;
71234
+ const member = memberForValueRow(row, ctx);
71235
+ if (member === null) return row;
71236
+ if (!isMemberListBase(member) && !isMemberDictionaryBase(member))
71237
+ return row;
71238
+ if (isMemberListBase(member) && listKindOf(member) === 1 /* Unordered */) {
71239
+ return { ...row, value: [] };
71240
+ }
71241
+ if (!assignedValues.has(row.value)) return row;
71242
+ const values = [];
71243
+ const declarations = /* @__PURE__ */ new Map();
71244
+ const genericEnv = runtimeGenericEnvForValueRow(row, ctx, /* @__PURE__ */ new Set());
71245
+ overlayConcreteGenericEntries(
71246
+ genericEnv,
71247
+ envFromStamp(row.genericBindings)
71248
+ );
71249
+ const materialized = materializeConstructorArgumentValue({
71250
+ member,
71251
+ value: row.value,
71252
+ genericEnv,
71253
+ ctx,
71254
+ createdValues: values,
71255
+ createdById: /* @__PURE__ */ new Map(),
71256
+ storageKeyDeclarations: declarations,
71257
+ collectionEntriesAreReferences: false,
71258
+ collectionOwnerId: row.id
71259
+ });
71260
+ const encoded = { ...row, value: materialized.value };
71261
+ const children = values.filter((value) => value !== materialized);
71262
+ declarations.delete(materialized.id);
71263
+ const groupId = constructorGroupForRow(row.id, ctx);
71264
+ retargetConstructorGroupDependencies(
71265
+ materialized.id,
71266
+ groupId ?? row.id,
71267
+ ctx
71268
+ );
71269
+ if (groupId !== null) {
71270
+ const state = ctx.__executionState;
71271
+ const group = state?.constructorGroups.get(groupId);
71272
+ for (const child of children) {
71273
+ retargetConstructorGroupDependencies(child.id, groupId, ctx);
71274
+ group?.add(child.id);
71275
+ state?.constructorGroupByRowId.set(child.id, groupId);
71276
+ }
71277
+ }
71278
+ stampCreatedValuesMapKey(children, row.mapKey);
71279
+ applyDeclaredStorageKeyOverrides({
71280
+ createdValues: [encoded, ...children],
71281
+ declarationByValueId: declarations
71282
+ });
71283
+ consumeBudget(
71284
+ ctx,
71285
+ "constructedSessionRows",
71286
+ children.length,
71287
+ "constructed Session row"
71288
+ );
71289
+ for (const child of children) {
71290
+ if (!isLiteralValueContent(child)) {
71291
+ throw new NSGetterRuntimeError(
71292
+ "Initializer collection produced an unevaluated row."
71293
+ );
71294
+ }
71295
+ extra.push(child);
71296
+ }
71297
+ for (const [id2, declaration] of declarations) {
71298
+ ctx.__createdStorageKeyDeclarations?.set(id2, declaration);
71299
+ }
71300
+ return encoded;
71301
+ });
71302
+ return [...exported, ...extra];
71303
+ }
71063
71304
  function evaluateNSGetter(getter, ctx) {
71064
71305
  return evaluateNSGetterWithEffects(getter, ctx).value;
71065
71306
  }
71066
- function evaluateNSGetterWithEffects(getter, ctx, argumentValues = []) {
71307
+ function evaluateNSGetterWithEffects(getter, ctx, argumentValues = [], options = {}) {
71067
71308
  const ownsInvocationState = ctx.__executionState === void 0;
71068
71309
  const requestedWrites = [];
71069
71310
  const runtimeCtx = withEvaluationRuntime(ctx, requestedWrites);
@@ -71093,6 +71334,27 @@ function evaluateNSGetterWithEffects(getter, ctx, argumentValues = []) {
71093
71334
  if (getter.typeInfo.type === 20 /* Decimal */ && typeof result.value === "number") {
71094
71335
  result.value = coerceDecimalOperand(result.value, "return");
71095
71336
  }
71337
+ if (options.exportInitializerValues) {
71338
+ const created = createdSessionValuesSince(
71339
+ runtimeCtx,
71340
+ creationCheckpoint
71341
+ );
71342
+ const rootIndex = created.findIndex(
71343
+ (row) => row.value === result.value
71344
+ );
71345
+ const exported = exportInitializerCollections(
71346
+ created,
71347
+ writes,
71348
+ runtimeCtx
71349
+ );
71350
+ if (exported !== created) {
71351
+ for (const row of exported)
71352
+ registerRuntimeSessionValue(runtimeCtx, row);
71353
+ invalidateEvaluatorIndexes(runtimeCtx, null);
71354
+ }
71355
+ const exportedRoot = exported[rootIndex];
71356
+ if (exportedRoot !== void 0) result.value = exportedRoot.value;
71357
+ }
71096
71358
  finalizeConstructorAllocations(
71097
71359
  runtimeCtx,
71098
71360
  result.value,
@@ -73454,6 +73716,18 @@ function evalPointer(pointer, scope, ctx) {
73454
73716
  state.functionStack.pop();
73455
73717
  }
73456
73718
  }
73719
+ case "objectInitializer" /* objectInitializer */: {
73720
+ const receiver = evalPointer(pointer.receiver.pointer, scope, ctx);
73721
+ const initializerScope = createChildScope(scope);
73722
+ initializerScope.setLocal(pointer.receiver.id, receiver);
73723
+ evalInstructions(
73724
+ pointer.assignments,
73725
+ initializerScope,
73726
+ ctx,
73727
+ evaluationOptions(ctx, true)
73728
+ );
73729
+ return receiver;
73730
+ }
73457
73731
  case "callDelegate" /* callDelegate */: {
73458
73732
  const delegate = evalPointer(pointer.delegate, scope, ctx);
73459
73733
  if (delegate === null || delegate === void 0) {
@@ -75172,8 +75446,6 @@ function evalTileConvert(pointer, scope, ctx) {
75172
75446
  );
75173
75447
  }
75174
75448
  if (receiverClassId === targetClassId) return true;
75175
- const storedReceiver = ctx.vm.databaseVM?.storedValueById?.(receiverRow.id) ?? ctx.vm.values.find((row) => row.id === receiverRow.id) ?? null;
75176
- if (storedReceiver === null) return false;
75177
75449
  const cell = readSystemNativeField(receiver, "Cell", ctx);
75178
75450
  const cellRow = trackedRowForValueReference(cell, ctx);
75179
75451
  const nextValue = { Cell: cellRow?.id ?? cell };
@@ -75194,6 +75466,13 @@ function evalTileConvert(pointer, scope, ctx) {
75194
75466
  targetClassId
75195
75467
  );
75196
75468
  ctx.__valueOverlay?.set(receiverRow.id, nextRow);
75469
+ const sessionRow = ctx.__runtimeSessionValues?.get(receiverRow.id);
75470
+ if (sessionRow !== void 0) {
75471
+ for (const key of Object.keys(sessionRow)) {
75472
+ if (!Object.hasOwn(nextRow, key)) Reflect.deleteProperty(sessionRow, key);
75473
+ }
75474
+ Object.assign(sessionRow, nextRow);
75475
+ }
75197
75476
  ctx.__executionState?.writes.push({
75198
75477
  kind: "replaceClass",
75199
75478
  target: receiver,
@@ -79429,20 +79708,25 @@ function materializeConstructorArgumentValue(args) {
79429
79708
  for (const entry of rows) sourceEntries.push(entry.value);
79430
79709
  }
79431
79710
  const ids = [];
79711
+ const referenceIndexes = args.ctx.__unorderedListProjections?.referenceEntryIndexesByArray.get(
79712
+ sourceEntries
79713
+ );
79432
79714
  for (const [index, rawEntry] of sourceEntries.entries()) {
79433
79715
  const entryValue = constructorCollectionEntryValue(
79434
79716
  rawEntry,
79435
79717
  args.ctx,
79436
- trackedValueId !== null
79718
+ isMemberClassBase(resolvedEntry) || isMemberListBase(resolvedEntry) || isMemberDictionaryBase(resolvedEntry) || (referenceIndexes?.has(index) ?? args.collectionEntriesAreReferences ?? trackedValueId !== null)
79437
79719
  );
79438
79720
  const child = materializeConstructorArgumentValue({
79439
79721
  ...args,
79722
+ collectionEntriesAreReferences: void 0,
79723
+ collectionOwnerId: void 0,
79440
79724
  member: resolvedEntry,
79441
79725
  genericEnv: entryEnv,
79442
79726
  value: entryValue,
79443
79727
  owner: {
79444
- identity: `value:${row.id}:index:${index}`,
79445
- label: row.id
79728
+ identity: `value:${args.collectionOwnerId ?? row.id}:index:${index}`,
79729
+ label: args.collectionOwnerId ?? row.id
79446
79730
  },
79447
79731
  destinationName: `${args.member.name}[${index}]`
79448
79732
  });
@@ -79479,16 +79763,18 @@ function materializeConstructorArgumentValue(args) {
79479
79763
  const entry = constructorCollectionEntryValue(
79480
79764
  rawEntry,
79481
79765
  args.ctx,
79482
- trackedValueId !== null
79766
+ isMemberClassBase(resolvedEntry) || isMemberListBase(resolvedEntry) || isMemberDictionaryBase(resolvedEntry) || (args.collectionEntriesAreReferences ?? trackedValueId !== null)
79483
79767
  );
79484
79768
  record6[key] = materializeConstructorArgumentValue({
79485
79769
  ...args,
79770
+ collectionEntriesAreReferences: void 0,
79771
+ collectionOwnerId: void 0,
79486
79772
  member: resolvedEntry,
79487
79773
  genericEnv: entryEnv,
79488
79774
  value: entry,
79489
79775
  owner: {
79490
- identity: `value:${row.id}:slot:${key}`,
79491
- label: row.id
79776
+ identity: `value:${args.collectionOwnerId ?? row.id}:slot:${key}`,
79777
+ label: args.collectionOwnerId ?? row.id
79492
79778
  },
79493
79779
  destinationName: `${args.member.name}[${key}]`
79494
79780
  }).id;
@@ -83485,7 +83771,8 @@ function evaluateMemberInitializer(args) {
83485
83771
  const result = evaluateNSGetterWithEffects(
83486
83772
  compiled,
83487
83773
  ctx,
83488
- args.argumentValues ?? []
83774
+ args.argumentValues ?? [],
83775
+ { exportInitializerValues: true }
83489
83776
  );
83490
83777
  const created = result.createdSessionValues ?? [];
83491
83778
  if (isMemberLookupBase(args.member)) {
@@ -94462,7 +94749,7 @@ function inspectCollection(options, member) {
94462
94749
  if (member.kind === 6 /* List */) {
94463
94750
  listKind = listKindOf(member);
94464
94751
  if (!isValidListValueShape(member, body)) {
94465
- const label = options.diagnosticStyle === "project" ? String(listKind) : ListKind[listKind].toLowerCase();
94752
+ const label = ListKind[listKind].toLowerCase();
94466
94753
  fail(`${options.path} has an invalid ${label} List value shape.`);
94467
94754
  }
94468
94755
  if (listKind === 1 /* Unordered */) body = [];
@@ -137435,7 +137722,7 @@ var init_registry2 = __esm({
137435
137722
  "schema-contract/registry.mjs"() {
137436
137723
  "use strict";
137437
137724
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
137438
- cliVersion: "0.52.3",
137725
+ cliVersion: "0.52.4",
137439
137726
  projectFileUploadBatchSize: 32,
137440
137727
  documentRecords: {
137441
137728
  member: {
@@ -141699,6 +141986,7 @@ async function runPendingMigrations(workspace, raw, migrations, onlyRef) {
141699
141986
  `Migration "${migrationName}" produced an invalid tile class replacement.`
141700
141987
  );
141701
141988
  }
141989
+ if (createdById.has(write.valueId)) continue;
141702
141990
  const storedRoot = valueById.get(write.valueId);
141703
141991
  if (storedRoot === void 0 || !isMemberValue(storedRoot)) {
141704
141992
  throw new Error(
@@ -145307,7 +145595,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
145307
145595
  async function main() {
145308
145596
  const args = parseArgs(process.argv.slice(2));
145309
145597
  if (args.command === "--version") {
145310
- console.log("0.52.3");
145598
+ console.log("0.52.4");
145311
145599
  return;
145312
145600
  }
145313
145601
  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.52.3",
3
+ "version": "0.52.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.52.3 -->
12
+ <!-- reviewed-through-cli: 0.52.4 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -97,7 +97,7 @@ wrappers.
97
97
  The marker near the top of `SKILL.md` must exactly match the package version:
98
98
 
99
99
  ```html
100
- <!-- reviewed-through-cli: 0.52.3 -->
100
+ <!-- reviewed-through-cli: 0.52.4 -->
101
101
  ```
102
102
 
103
103
  The quoted version above is checked too, so this instruction cannot go stale
@@ -120,6 +120,16 @@ runtime Class's one-argument `Equals` Function or NSFunction when it returns
120
120
  `bool`, including the effective override. Values without that member fall back
121
121
  to ordinary NeoScript equality.
122
122
 
123
+ Object initializers are available in method bodies and `.spec.neo` tests:
124
+
125
+ ```neo
126
+ var tile = new DirtWetTile { Cell = Vector2Int(3, -2) };
127
+ ```
128
+
129
+ Construction runs first. Each initializer entry then uses ordinary assignment
130
+ rules in source order, including property setters, access checks, and readonly
131
+ restrictions. Initializer expressions read the caller's scope.
132
+
123
133
  ## Nullability and ownership
124
134
 
125
135
  Do not access a nullable receiver directly. First narrow it with a condition, or