@neocompose/cli 0.52.3 → 0.52.5

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.52.5] - 2026-09-17
4
+
5
+ - Materialize computed collection defaults, including `List.Repeat<T?>(null, count)`, as stored entries when seeding new instances.
6
+
7
+ ## [0.52.4] - 2026-09-17
8
+
9
+ - Accept declared list-index lookups in constructor arguments, including readable string keys such as `root.Assets.Items.Index.Slug["stone"]!`.
10
+ - Convert session-constructed tiles with either `TryConvert` overload, preserving their identity and Cell, and persist their final class in migrations.
11
+ - 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.
12
+ - 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.
13
+
3
14
  ## [0.52.3] - 2026-09-17
4
15
 
5
16
  - 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);
@@ -54907,7 +55060,8 @@ function evaluateLiteralContainer(args) {
54907
55060
  const evaluated = args.initEvaluator(
54908
55061
  args.member,
54909
55062
  body.init,
54910
- args.sourceValueId
55063
+ args.sourceValueId,
55064
+ args.genericEnv
54911
55065
  );
54912
55066
  const literal3 = {
54913
55067
  value: evaluated.value,
@@ -54960,6 +55114,7 @@ function buildDefaultMemberValue(args) {
54960
55114
  if (documentHasInitValueContent(args.document, declaredDefault)) {
54961
55115
  const initializerCreatedValuesStart = args.createdValues.length;
54962
55116
  const evaluated = evaluateLiteralContainer({
55117
+ genericEnv: args.genericEnv,
54963
55118
  body: declaredDefault,
54964
55119
  member,
54965
55120
  initEvaluator: args.initEvaluator
@@ -54981,7 +55136,7 @@ function buildDefaultMemberValue(args) {
54981
55136
  value2.genericBindings = { ...evaluated.genericBindings };
54982
55137
  }
54983
55138
  if (evaluated.provisionalRootId !== void 0) {
54984
- retargetDelegateReceiverValueIds(
55139
+ retargetInitializerRootValueIds(
54985
55140
  [value2, ...args.createdValues.slice(initializerCreatedValuesStart)],
54986
55141
  evaluated.provisionalRootId,
54987
55142
  value2.id
@@ -55283,7 +55438,7 @@ function buildNewValue(projectId, body, timestamp2 = Date.now()) {
55283
55438
  }
55284
55439
  return props;
55285
55440
  }
55286
- function retargetDelegateReceiverValueIds(rows, fromValueId, toValueId) {
55441
+ function retargetInitializerRootValueIds(rows, fromValueId, toValueId) {
55287
55442
  const visited = /* @__PURE__ */ new WeakSet();
55288
55443
  const visit = (value) => {
55289
55444
  if (typeof value !== "object" || value === null) return;
@@ -55300,6 +55455,7 @@ function retargetDelegateReceiverValueIds(rows, fromValueId, toValueId) {
55300
55455
  for (const entry of Object.values(value)) visit(entry);
55301
55456
  };
55302
55457
  for (const row of rows) {
55458
+ if (row.containerId === fromValueId) row.containerId = toValueId;
55303
55459
  if (!isLiteralValueContent(row)) continue;
55304
55460
  visit(row.value);
55305
55461
  visit(row.constructorArgs);
@@ -55429,6 +55585,7 @@ function cloneDefaultValueForMember(args) {
55429
55585
  if (documentHasInitValueContent(args.document, sourceValue)) {
55430
55586
  const initializerCreatedValuesStart = args.createdValues.length;
55431
55587
  const evaluated = evaluateLiteralContainer({
55588
+ genericEnv: args.genericEnv,
55432
55589
  body: sourceValue,
55433
55590
  member,
55434
55591
  initEvaluator: args.initEvaluator,
@@ -55451,7 +55608,7 @@ function cloneDefaultValueForMember(args) {
55451
55608
  evaluatedRow.genericBindings = { ...evaluated.genericBindings };
55452
55609
  }
55453
55610
  if (evaluated.provisionalRootId !== void 0) {
55454
- retargetDelegateReceiverValueIds(
55611
+ retargetInitializerRootValueIds(
55455
55612
  [
55456
55613
  evaluatedRow,
55457
55614
  ...args.createdValues.slice(initializerCreatedValuesStart)
@@ -71060,10 +71217,98 @@ function createdSessionValuesSince(ctx, checkpoint) {
71060
71217
  }
71061
71218
  return created;
71062
71219
  }
71220
+ function exportInitializerCollections(created, writes, ctx) {
71221
+ const assignedValues = /* @__PURE__ */ new Set();
71222
+ for (const write of writes) {
71223
+ if (write.kind === "assign" && write.valueId === void 0 && typeof write.value === "object" && write.value !== null) {
71224
+ assignedValues.add(write.value);
71225
+ }
71226
+ }
71227
+ const candidates = /* @__PURE__ */ new Set();
71228
+ for (const row of created) {
71229
+ if (assignedValues.has(row.value) || Array.isArray(row.value) && row.value.length > 0 && ctx.__unorderedListProjections?.byContainerId.has(row.id)) {
71230
+ candidates.add(row.id);
71231
+ }
71232
+ }
71233
+ if (candidates.size === 0) return created;
71234
+ invalidateEvaluatorIndexes(ctx, null);
71235
+ const extra = [];
71236
+ const exported = created.map((row) => {
71237
+ if (!candidates.has(row.id)) return row;
71238
+ const member = memberForValueRow(row, ctx);
71239
+ if (member === null) return row;
71240
+ if (!isMemberListBase(member) && !isMemberDictionaryBase(member))
71241
+ return row;
71242
+ if (isMemberListBase(member) && listKindOf(member) === 1 /* Unordered */) {
71243
+ return { ...row, value: [] };
71244
+ }
71245
+ if (!assignedValues.has(row.value)) return row;
71246
+ const values = [];
71247
+ const declarations = /* @__PURE__ */ new Map();
71248
+ const genericEnv = runtimeGenericEnvForValueRow(row, ctx, /* @__PURE__ */ new Set());
71249
+ overlayConcreteGenericEntries(
71250
+ genericEnv,
71251
+ envFromStamp(row.genericBindings)
71252
+ );
71253
+ const materialized = materializeConstructorArgumentValue({
71254
+ member,
71255
+ value: row.value,
71256
+ genericEnv,
71257
+ ctx,
71258
+ createdValues: values,
71259
+ createdById: /* @__PURE__ */ new Map(),
71260
+ storageKeyDeclarations: declarations,
71261
+ collectionEntriesAreReferences: false,
71262
+ collectionOwnerId: row.id
71263
+ });
71264
+ const encoded = { ...row, value: materialized.value };
71265
+ const children = values.filter((value) => value !== materialized);
71266
+ declarations.delete(materialized.id);
71267
+ const groupId = constructorGroupForRow(row.id, ctx);
71268
+ retargetConstructorGroupDependencies(
71269
+ materialized.id,
71270
+ groupId ?? row.id,
71271
+ ctx
71272
+ );
71273
+ if (groupId !== null) {
71274
+ const state = ctx.__executionState;
71275
+ const group = state?.constructorGroups.get(groupId);
71276
+ for (const child of children) {
71277
+ retargetConstructorGroupDependencies(child.id, groupId, ctx);
71278
+ group?.add(child.id);
71279
+ state?.constructorGroupByRowId.set(child.id, groupId);
71280
+ }
71281
+ }
71282
+ stampCreatedValuesMapKey(children, row.mapKey);
71283
+ applyDeclaredStorageKeyOverrides({
71284
+ createdValues: [encoded, ...children],
71285
+ declarationByValueId: declarations
71286
+ });
71287
+ consumeBudget(
71288
+ ctx,
71289
+ "constructedSessionRows",
71290
+ children.length,
71291
+ "constructed Session row"
71292
+ );
71293
+ for (const child of children) {
71294
+ if (!isLiteralValueContent(child)) {
71295
+ throw new NSGetterRuntimeError(
71296
+ "Initializer collection produced an unevaluated row."
71297
+ );
71298
+ }
71299
+ extra.push(child);
71300
+ }
71301
+ for (const [id2, declaration] of declarations) {
71302
+ ctx.__createdStorageKeyDeclarations?.set(id2, declaration);
71303
+ }
71304
+ return encoded;
71305
+ });
71306
+ return [...exported, ...extra];
71307
+ }
71063
71308
  function evaluateNSGetter(getter, ctx) {
71064
71309
  return evaluateNSGetterWithEffects(getter, ctx).value;
71065
71310
  }
71066
- function evaluateNSGetterWithEffects(getter, ctx, argumentValues = []) {
71311
+ function evaluateNSGetterWithEffects(getter, ctx, argumentValues = [], options = {}) {
71067
71312
  const ownsInvocationState = ctx.__executionState === void 0;
71068
71313
  const requestedWrites = [];
71069
71314
  const runtimeCtx = withEvaluationRuntime(ctx, requestedWrites);
@@ -71093,6 +71338,50 @@ function evaluateNSGetterWithEffects(getter, ctx, argumentValues = []) {
71093
71338
  if (getter.typeInfo.type === 20 /* Decimal */ && typeof result.value === "number") {
71094
71339
  result.value = coerceDecimalOperand(result.value, "return");
71095
71340
  }
71341
+ const initializerResult = options.initializerResult;
71342
+ if (initializerResult !== void 0 && result.value !== null && (isMemberListBase(initializerResult.member) || isMemberDictionaryBase(initializerResult.member)) && findKnownRowIdByValueReference(result.value, runtimeCtx) === null) {
71343
+ const createdValues = createdSessionValuesSince(runtimeCtx, creationCheckpoint);
71344
+ const declarations = /* @__PURE__ */ new Map();
71345
+ const root = materializeConstructorArgumentValue({
71346
+ member: initializerResult.member,
71347
+ value: result.value,
71348
+ genericEnv: initializerResult.genericEnv ?? /* @__PURE__ */ new Map(),
71349
+ ctx: runtimeCtx,
71350
+ createdValues,
71351
+ createdById: new Map(createdValues.map((row) => [row.id, row])),
71352
+ storageKeyDeclarations: declarations,
71353
+ collectionEntriesAreReferences: false
71354
+ });
71355
+ declarations.delete(root.id);
71356
+ publishConstructedRows({
71357
+ root,
71358
+ createdValues,
71359
+ storageKeyDeclarations: declarations,
71360
+ ctx: runtimeCtx
71361
+ });
71362
+ result.value = root.value;
71363
+ }
71364
+ if (options.exportInitializerValues) {
71365
+ const created = createdSessionValuesSince(
71366
+ runtimeCtx,
71367
+ creationCheckpoint
71368
+ );
71369
+ const rootIndex = created.findIndex(
71370
+ (row) => row.value === result.value
71371
+ );
71372
+ const exported = exportInitializerCollections(
71373
+ created,
71374
+ writes,
71375
+ runtimeCtx
71376
+ );
71377
+ if (exported !== created) {
71378
+ for (const row of exported)
71379
+ registerRuntimeSessionValue(runtimeCtx, row);
71380
+ invalidateEvaluatorIndexes(runtimeCtx, null);
71381
+ }
71382
+ const exportedRoot = exported[rootIndex];
71383
+ if (exportedRoot !== void 0) result.value = exportedRoot.value;
71384
+ }
71096
71385
  finalizeConstructorAllocations(
71097
71386
  runtimeCtx,
71098
71387
  result.value,
@@ -73454,6 +73743,18 @@ function evalPointer(pointer, scope, ctx) {
73454
73743
  state.functionStack.pop();
73455
73744
  }
73456
73745
  }
73746
+ case "objectInitializer" /* objectInitializer */: {
73747
+ const receiver = evalPointer(pointer.receiver.pointer, scope, ctx);
73748
+ const initializerScope = createChildScope(scope);
73749
+ initializerScope.setLocal(pointer.receiver.id, receiver);
73750
+ evalInstructions(
73751
+ pointer.assignments,
73752
+ initializerScope,
73753
+ ctx,
73754
+ evaluationOptions(ctx, true)
73755
+ );
73756
+ return receiver;
73757
+ }
73457
73758
  case "callDelegate" /* callDelegate */: {
73458
73759
  const delegate = evalPointer(pointer.delegate, scope, ctx);
73459
73760
  if (delegate === null || delegate === void 0) {
@@ -74723,17 +75024,19 @@ function readDeclarationDefault(member, ctx) {
74723
75024
  projectId: ctx.vm.project.id,
74724
75025
  member,
74725
75026
  createdValues: created,
74726
- initEvaluator: (child, init, sourceValueId) => withConstructionGenericSlots(
75027
+ initEvaluator: (child, init, sourceValueId, genericEnv = /* @__PURE__ */ new Map()) => withConstructionGenericSlots(
74727
75028
  child,
74728
75029
  ctx,
74729
- /* @__PURE__ */ new Map(),
75030
+ genericEnv,
74730
75031
  () => evaluateInitializerInContext(
74731
75032
  init,
74732
75033
  child,
74733
75034
  ctx,
74734
75035
  created,
74735
75036
  [],
74736
- sourceValueId
75037
+ sourceValueId,
75038
+ void 0,
75039
+ genericEnv
74737
75040
  )
74738
75041
  )
74739
75042
  });
@@ -75172,8 +75475,6 @@ function evalTileConvert(pointer, scope, ctx) {
75172
75475
  );
75173
75476
  }
75174
75477
  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
75478
  const cell = readSystemNativeField(receiver, "Cell", ctx);
75178
75479
  const cellRow = trackedRowForValueReference(cell, ctx);
75179
75480
  const nextValue = { Cell: cellRow?.id ?? cell };
@@ -75194,6 +75495,13 @@ function evalTileConvert(pointer, scope, ctx) {
75194
75495
  targetClassId
75195
75496
  );
75196
75497
  ctx.__valueOverlay?.set(receiverRow.id, nextRow);
75498
+ const sessionRow = ctx.__runtimeSessionValues?.get(receiverRow.id);
75499
+ if (sessionRow !== void 0) {
75500
+ for (const key of Object.keys(sessionRow)) {
75501
+ if (!Object.hasOwn(nextRow, key)) Reflect.deleteProperty(sessionRow, key);
75502
+ }
75503
+ Object.assign(sessionRow, nextRow);
75504
+ }
75197
75505
  ctx.__executionState?.writes.push({
75198
75506
  kind: "replaceClass",
75199
75507
  target: receiver,
@@ -77517,7 +77825,7 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
77517
77825
  }
77518
77826
  return null;
77519
77827
  };
77520
- return (member, init, sourceValueId) => {
77828
+ return (member, init, sourceValueId, genericEnv = instanceEnv) => {
77521
77829
  const memberId = Reflect.get(member, "id");
77522
77830
  const effectiveMemberId = typeof memberId === "string" ? memberId : null;
77523
77831
  const scopeMemberId = indexes2.initializerScopeMemberIds.get(init) ?? (typeof sourceValueId !== "string" ? void 0 : indexes2.initializerScopeMemberIdsByValueId.get(sourceValueId)) ?? effectiveMemberId;
@@ -77532,7 +77840,7 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
77532
77840
  return withConstructionGenericSlots(
77533
77841
  member,
77534
77842
  ctx,
77535
- instanceEnv,
77843
+ genericEnv,
77536
77844
  () => evaluateInitializerInContext(
77537
77845
  init,
77538
77846
  member,
@@ -77540,7 +77848,8 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
77540
77848
  createdValues,
77541
77849
  argumentValues,
77542
77850
  sourceValueId ?? null,
77543
- scopeMemberId
77851
+ scopeMemberId,
77852
+ genericEnv
77544
77853
  )
77545
77854
  );
77546
77855
  };
@@ -77677,7 +77986,7 @@ function runtimeValueReferencesVariable(value, variableId, visited = /* @__PURE_
77677
77986
  (child) => runtimeValueReferencesVariable(child, variableId, visited)
77678
77987
  );
77679
77988
  }
77680
- function evaluateInitializerInContext(init, member, ctx, createdValues, argumentValues = [], sourceValueId = null, compileMemberId) {
77989
+ function evaluateInitializerInContext(init, member, ctx, createdValues, argumentValues = [], sourceValueId = null, compileMemberId, genericEnv = /* @__PURE__ */ new Map()) {
77681
77990
  ctx.__constructedArgumentsByValue ??= /* @__PURE__ */ new WeakMap();
77682
77991
  const compiled = ensureInitializerCompiled({
77683
77992
  init,
@@ -77693,7 +78002,8 @@ function evaluateInitializerInContext(init, member, ctx, createdValues, argument
77693
78002
  result = evaluateNSGetterWithEffects(
77694
78003
  compiled,
77695
78004
  initializerCtx,
77696
- argumentValues
78005
+ argumentValues,
78006
+ { initializerResult: { member, genericEnv } }
77697
78007
  );
77698
78008
  } finally {
77699
78009
  closeFrame();
@@ -79341,6 +79651,9 @@ function cloneConstructorLiteralValue(value) {
79341
79651
  }
79342
79652
  function materializeConstructorArgumentValue(args) {
79343
79653
  const trackedValueId = findKnownRowIdByValueReference(args.value, args.ctx);
79654
+ if (args.value === null && !isRequiredMember2(args.member) && (isMemberListBase(args.member) || isMemberDictionaryBase(args.member))) {
79655
+ return registerConstructorArgumentRow(args, null);
79656
+ }
79344
79657
  if (isMemberClassBase(args.member)) {
79345
79658
  if (args.value === null || args.value === void 0) {
79346
79659
  if (isRequiredMember2(args.member)) {
@@ -79429,20 +79742,25 @@ function materializeConstructorArgumentValue(args) {
79429
79742
  for (const entry of rows) sourceEntries.push(entry.value);
79430
79743
  }
79431
79744
  const ids = [];
79745
+ const referenceIndexes = args.ctx.__unorderedListProjections?.referenceEntryIndexesByArray.get(
79746
+ sourceEntries
79747
+ );
79432
79748
  for (const [index, rawEntry] of sourceEntries.entries()) {
79433
79749
  const entryValue = constructorCollectionEntryValue(
79434
79750
  rawEntry,
79435
79751
  args.ctx,
79436
- trackedValueId !== null
79752
+ isMemberClassBase(resolvedEntry) || isMemberListBase(resolvedEntry) || isMemberDictionaryBase(resolvedEntry) || (referenceIndexes?.has(index) ?? args.collectionEntriesAreReferences ?? trackedValueId !== null)
79437
79753
  );
79438
79754
  const child = materializeConstructorArgumentValue({
79439
79755
  ...args,
79756
+ collectionEntriesAreReferences: void 0,
79757
+ collectionOwnerId: void 0,
79440
79758
  member: resolvedEntry,
79441
79759
  genericEnv: entryEnv,
79442
79760
  value: entryValue,
79443
79761
  owner: {
79444
- identity: `value:${row.id}:index:${index}`,
79445
- label: row.id
79762
+ identity: `value:${args.collectionOwnerId ?? row.id}:index:${index}`,
79763
+ label: args.collectionOwnerId ?? row.id
79446
79764
  },
79447
79765
  destinationName: `${args.member.name}[${index}]`
79448
79766
  });
@@ -79479,16 +79797,18 @@ function materializeConstructorArgumentValue(args) {
79479
79797
  const entry = constructorCollectionEntryValue(
79480
79798
  rawEntry,
79481
79799
  args.ctx,
79482
- trackedValueId !== null
79800
+ isMemberClassBase(resolvedEntry) || isMemberListBase(resolvedEntry) || isMemberDictionaryBase(resolvedEntry) || (args.collectionEntriesAreReferences ?? trackedValueId !== null)
79483
79801
  );
79484
79802
  record6[key] = materializeConstructorArgumentValue({
79485
79803
  ...args,
79804
+ collectionEntriesAreReferences: void 0,
79805
+ collectionOwnerId: void 0,
79486
79806
  member: resolvedEntry,
79487
79807
  genericEnv: entryEnv,
79488
79808
  value: entry,
79489
79809
  owner: {
79490
- identity: `value:${row.id}:slot:${key}`,
79491
- label: row.id
79810
+ identity: `value:${args.collectionOwnerId ?? row.id}:slot:${key}`,
79811
+ label: args.collectionOwnerId ?? row.id
79492
79812
  },
79493
79813
  destinationName: `${args.member.name}[${key}]`
79494
79814
  }).id;
@@ -79669,7 +79989,8 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
79669
79989
  initializerCreatedValues,
79670
79990
  argumentValues,
79671
79991
  sourceRow.id,
79672
- compileMemberId
79992
+ compileMemberId,
79993
+ isMemberListBase(sourceMember) || isMemberDictionaryBase(sourceMember) ? runtimeGenericEnvForValueRow(sourceRow, ctx, /* @__PURE__ */ new Set()) : void 0
79673
79994
  );
79674
79995
  if (evaluated.existingValueRow !== void 0) {
79675
79996
  const adoptedClone = cloneRow2(
@@ -79704,7 +80025,7 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
79704
80025
  Reflect.deleteProperty(clone, "containerId");
79705
80026
  }
79706
80027
  if (evaluated.provisionalRootId !== void 0) {
79707
- retargetDelegateReceiverValueIds(
80028
+ retargetInitializerRootValueIds(
79708
80029
  [clone, ...initializerCreatedValues],
79709
80030
  evaluated.provisionalRootId,
79710
80031
  clone.id
@@ -80269,6 +80590,7 @@ function evaluateInitializerMaterialization(args) {
80269
80590
  const createdValues = [];
80270
80591
  const storageKeyDeclarations = /* @__PURE__ */ new Map();
80271
80592
  const evaluated = args.evaluateInitializer({
80593
+ genericEnv: args.genericEnv,
80272
80594
  init: args.init,
80273
80595
  member: args.member,
80274
80596
  document: args.document,
@@ -80317,6 +80639,7 @@ function materializeInitializerValue(args) {
80317
80639
  ...args.storedConstructionReplay === true ? { storedConstructionReplay: true } : {},
80318
80640
  ...args.constructionBaselineReplay === true ? { constructionBaselineReplay: true } : {},
80319
80641
  ...args.argumentValues === void 0 ? {} : { argumentValues: args.argumentValues },
80642
+ genericEnv: envFromStamp(args.row.genericBindings),
80320
80643
  sourceValueId: args.row.id,
80321
80644
  readTracking: args.readTracking,
80322
80645
  saveStaticBindings: args.saveStaticBindings,
@@ -80345,7 +80668,7 @@ function materializeInitializerValue(args) {
80345
80668
  };
80346
80669
  const allCreated = [root, ...createdValues];
80347
80670
  if (evaluated.provisionalRootId !== void 0) {
80348
- retargetDelegateReceiverValueIds(
80671
+ retargetInitializerRootValueIds(
80349
80672
  allCreated,
80350
80673
  evaluated.provisionalRootId,
80351
80674
  root.id
@@ -80408,7 +80731,9 @@ function materializeMemberDefaultValue(args) {
80408
80731
  ...args.genericEnv === void 0 ? {} : { genericEnv: args.genericEnv },
80409
80732
  // A declaration default may itself be init-backed one level down (P43 §2),
80410
80733
  // and those nested initializers must evaluate against the same document.
80411
- initEvaluator: (member, init, sourceValueId) => args.evaluateInitializer({
80734
+ initEvaluator: (member, init, sourceValueId, genericEnv) => args.evaluateInitializer({
80735
+ genericEnv,
80736
+ storageKeyDeclarations,
80412
80737
  init,
80413
80738
  member,
80414
80739
  document: args.document,
@@ -80433,7 +80758,7 @@ function materializeMemberDefaultValue(args) {
80433
80758
  ...built.genericBindings == null ? {} : { genericBindings: { ...built.genericBindings } }
80434
80759
  };
80435
80760
  const allCreated = [root, ...interior];
80436
- retargetDelegateReceiverValueIds(allCreated, built.id, root.id);
80761
+ retargetInitializerRootValueIds(allCreated, built.id, root.id);
80437
80762
  finalizeMaterializedRoot({
80438
80763
  root,
80439
80764
  allCreated,
@@ -80450,6 +80775,7 @@ var init_initializer_materialization = __esm({
80450
80775
  init_deep_clone_plain_data();
80451
80776
  init_members();
80452
80777
  init_inheritance();
80778
+ init_generics();
80453
80779
  init_evaluateNSGetter();
80454
80780
  init_initializer_context();
80455
80781
  init_value_row_owner_members();
@@ -83485,7 +83811,11 @@ function evaluateMemberInitializer(args) {
83485
83811
  const result = evaluateNSGetterWithEffects(
83486
83812
  compiled,
83487
83813
  ctx,
83488
- args.argumentValues ?? []
83814
+ args.argumentValues ?? [],
83815
+ {
83816
+ exportInitializerValues: true,
83817
+ initializerResult: { member: args.member, genericEnv: args.genericEnv }
83818
+ }
83489
83819
  );
83490
83820
  const created = result.createdSessionValues ?? [];
83491
83821
  if (isMemberLookupBase(args.member)) {
@@ -94462,7 +94792,7 @@ function inspectCollection(options, member) {
94462
94792
  if (member.kind === 6 /* List */) {
94463
94793
  listKind = listKindOf(member);
94464
94794
  if (!isValidListValueShape(member, body)) {
94465
- const label = options.diagnosticStyle === "project" ? String(listKind) : ListKind[listKind].toLowerCase();
94795
+ const label = ListKind[listKind].toLowerCase();
94466
94796
  fail(`${options.path} has an invalid ${label} List value shape.`);
94467
94797
  }
94468
94798
  if (listKind === 1 /* Unordered */) body = [];
@@ -98696,7 +99026,8 @@ function isStringValueRecord(value) {
98696
99026
  return value !== null && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
98697
99027
  }
98698
99028
  function commitInitEvaluator(document, createdValues) {
98699
- return (member, init, sourceValueId) => evaluateMemberInitializer({
99029
+ return (member, init, sourceValueId, genericEnv) => evaluateMemberInitializer({
99030
+ genericEnv,
98700
99031
  init,
98701
99032
  member,
98702
99033
  document,
@@ -98715,10 +99046,19 @@ function assertConstructedGraphReferencesResolve(args) {
98715
99046
  const valueIds = new Set(args.document.values.map((value) => value.id));
98716
99047
  for (const site of sites) {
98717
99048
  if ((site.init.compiled?.parameters.length ?? 2) > 2) continue;
99049
+ if (site.sourceValueId === void 0 && [
99050
+ ...referencedGenericParams(
99051
+ resolveMember2(site.member, args.document.members),
99052
+ args.document.members
99053
+ )
99054
+ ].some((paramId) => site.genericEnv.get(paramId)?.kind !== "member"))
99055
+ continue;
98718
99056
  const createdValues = [];
98719
99057
  const produced = evaluateMemberInitializer({
98720
99058
  init: site.init,
98721
99059
  member: site.member,
99060
+ genericEnv: site.genericEnv,
99061
+ sourceValueId: site.sourceValueId,
98722
99062
  document: args.document,
98723
99063
  createdValues
98724
99064
  });
@@ -98750,6 +99090,10 @@ function collectTouchedInitializerSites(args) {
98750
99090
  if (!isInitValueContent(member.defaultValue)) continue;
98751
99091
  if (!changedConstructor && !changedMemberIds.has(member.id)) continue;
98752
99092
  sites.push({
99093
+ genericEnv: resolveDeclaringClassGenericEnv(
99094
+ member.id,
99095
+ args.document.classes
99096
+ ),
98753
99097
  label: `Member "${member.name}" (${member.id})`,
98754
99098
  member,
98755
99099
  init: member.defaultValue.init
@@ -98777,6 +99121,8 @@ function collectTouchedInitializerSites(args) {
98777
99121
  );
98778
99122
  }
98779
99123
  sites.push({
99124
+ genericEnv: envFromStamp(row.genericBindings),
99125
+ sourceValueId: row.id,
98780
99126
  label: `Value "${row.id}"`,
98781
99127
  member,
98782
99128
  init: row.init
@@ -98793,11 +99139,19 @@ function assertProducedGraphLookupsResolve(args) {
98793
99139
  projectId: args.document.project.id,
98794
99140
  value: args.produced.value,
98795
99141
  classId: args.produced.classId,
99142
+ ...args.produced.genericBindings === void 0 ? {} : { genericBindings: { ...args.produced.genericBindings } },
98796
99143
  ...args.produced.constructorArgs === void 0 ? {} : { constructorArgs: args.produced.constructorArgs },
98797
99144
  ...args.produced.instanceConstructorId === void 0 ? {} : { instanceConstructorId: args.produced.instanceConstructorId },
98798
99145
  createdAt: 0,
98799
99146
  updatedAt: 0
98800
99147
  };
99148
+ if (args.produced.provisionalRootId !== void 0) {
99149
+ retargetInitializerRootValueIds(
99150
+ graphValues,
99151
+ args.produced.provisionalRootId,
99152
+ rootRow.id
99153
+ );
99154
+ }
98801
99155
  const ownerByValueId = resolveSemanticOwnerMembersForValues(
98802
99156
  { ...args.document, values: [...graphValues, rootRow] },
98803
99157
  new Set(args.createdValues.map((value) => value.id)),
@@ -137435,7 +137789,7 @@ var init_registry2 = __esm({
137435
137789
  "schema-contract/registry.mjs"() {
137436
137790
  "use strict";
137437
137791
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
137438
- cliVersion: "0.52.3",
137792
+ cliVersion: "0.52.5",
137439
137793
  projectFileUploadBatchSize: 32,
137440
137794
  documentRecords: {
137441
137795
  member: {
@@ -141699,6 +142053,7 @@ async function runPendingMigrations(workspace, raw, migrations, onlyRef) {
141699
142053
  `Migration "${migrationName}" produced an invalid tile class replacement.`
141700
142054
  );
141701
142055
  }
142056
+ if (createdById.has(write.valueId)) continue;
141702
142057
  const storedRoot = valueById.get(write.valueId);
141703
142058
  if (storedRoot === void 0 || !isMemberValue(storedRoot)) {
141704
142059
  throw new Error(
@@ -145307,7 +145662,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
145307
145662
  async function main() {
145308
145663
  const args = parseArgs(process.argv.slice(2));
145309
145664
  if (args.command === "--version") {
145310
- console.log("0.52.3");
145665
+ console.log("0.52.5");
145311
145666
  return;
145312
145667
  }
145313
145668
  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.5",
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.5 -->
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.5 -->
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