@neocompose/cli 0.31.6 → 0.31.8

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.8] - 2026-08-17
4
+
5
+ ### Fixed
6
+
7
+ - Propagate `?.` through following required member and index accesses in the
8
+ same expression chain, so a nullable parent safely short-circuits a deep
9
+ read while independently nullable descendants still require explicit null
10
+ handling.
11
+
12
+ ## [0.31.7] - 2026-08-15
13
+
14
+ ### Fixed
15
+
16
+ - Preserve forwarded type arguments in constructed generic constraints such
17
+ as `TItem extends InventoryItem<TItemData>` through push, pull, stored-body
18
+ replay, validation, and C# generation.
19
+
3
20
  ## [0.31.6] - 2026-08-15
4
21
 
5
22
  ### Added
package/dist/neo.mjs CHANGED
@@ -11838,13 +11838,15 @@ var init_strict_resolver = __esm({
11838
11838
  };
11839
11839
  }
11840
11840
  }
11841
- const receiver = this.resolveExpression(receiverAst, scope);
11842
- if (optional && !isNullable(receiver.type)) {
11841
+ const resolvedReceiver = this.resolveExpression(receiverAst, scope);
11842
+ if (optional && !isNullable(resolvedReceiver.type)) {
11843
11843
  throw new CompileError(
11844
- `\`?.\` requires an optional receiver; got ${this.describe(receiver.type)}`,
11844
+ `\`?.\` requires an optional receiver; got ${this.describe(resolvedReceiver.type)}`,
11845
11845
  pos
11846
11846
  );
11847
11847
  }
11848
+ const receiver = resolvedReceiver.optionalChainType ? { ...resolvedReceiver, type: resolvedReceiver.optionalChainType } : resolvedReceiver;
11849
+ const nullPropagating = optional || resolvedReceiver.optionalChainType !== void 0;
11848
11850
  if (!optional && isNullable(receiver.type)) {
11849
11851
  throw new CompileError(
11850
11852
  `Member '${name}' is read on ${this.describe(receiver.type)}, which may be null. Handle null before reading through it \u2014 use \`!\` if it is always set, or \`?.\`, \`??\`, or a null check that narrows it.`,
@@ -11852,7 +11854,7 @@ var init_strict_resolver = __esm({
11852
11854
  );
11853
11855
  }
11854
11856
  if (receiver.staticType?.kind === "enum") {
11855
- if (optional) {
11857
+ if (nullPropagating) {
11856
11858
  throw new CompileError(
11857
11859
  `\`?.\` is not valid on enum type '${receiver.staticType.name}'; enum option access is always defined`,
11858
11860
  pos
@@ -11924,9 +11926,10 @@ var init_strict_resolver = __esm({
11924
11926
  pointer: receiver.pointer,
11925
11927
  key: numberPointer(0)
11926
11928
  },
11927
- ...optional ? { optional: true } : {}
11929
+ ...nullPropagating ? { optional: true } : {}
11928
11930
  },
11929
- type: optional ? { ...primitiveMember.type, nullable: true } : primitiveMember.type,
11931
+ type: nullPropagating ? { ...primitiveMember.type, nullable: true } : primitiveMember.type,
11932
+ ...nullPropagating ? { optionalChainType: primitiveMember.type } : {},
11930
11933
  ...receiver.writability ? { writability: receiver.writability } : {}
11931
11934
  };
11932
11935
  }
@@ -11934,7 +11937,7 @@ var init_strict_resolver = __esm({
11934
11937
  receiver,
11935
11938
  primitiveMember.key ?? name,
11936
11939
  primitiveMember.type,
11937
- optional
11940
+ nullPropagating
11938
11941
  );
11939
11942
  if (!primitiveMember.derived) return resolved;
11940
11943
  return {
@@ -11968,7 +11971,7 @@ var init_strict_resolver = __esm({
11968
11971
  receiver,
11969
11972
  name,
11970
11973
  { kind: "primitive", name: "string" },
11971
- optional
11974
+ nullPropagating
11972
11975
  );
11973
11976
  }
11974
11977
  const member = type.members.find((candidate) => candidate.name === name);
@@ -12002,9 +12005,10 @@ var init_strict_resolver = __esm({
12002
12005
  type: "callGetter" /* CallGetter */,
12003
12006
  memberId: member.id,
12004
12007
  receiver: { kind: "instance", pointer: receiver.pointer },
12005
- ...optional ? { optional: true } : {}
12008
+ ...nullPropagating ? { optional: true } : {}
12006
12009
  },
12007
- type: optional ? { ...memberType2, nullable: true } : memberType2,
12010
+ type: nullPropagating ? { ...memberType2, nullable: true } : memberType2,
12011
+ ...nullPropagating ? { optionalChainType: memberType2 } : {},
12008
12012
  symbol: member,
12009
12013
  ...member.writable ? {
12010
12014
  writability: member.writability ? toWritability(member.writability) : "setter" /* Setter */
@@ -12028,7 +12032,7 @@ var init_strict_resolver = __esm({
12028
12032
  receiver,
12029
12033
  member.schemaKey ?? member.name,
12030
12034
  memberType2,
12031
- optional,
12035
+ nullPropagating,
12032
12036
  member.id
12033
12037
  ),
12034
12038
  symbol: member,
@@ -12136,14 +12140,23 @@ var init_strict_resolver = __esm({
12136
12140
  ...optional ? { optional: true } : {}
12137
12141
  },
12138
12142
  type: optional ? { ...type, nullable: true } : type,
12143
+ ...optional ? { optionalChainType: type } : {},
12139
12144
  ...receiver.writability ? { writability: receiver.writability } : {}
12140
12145
  };
12141
12146
  }
12142
12147
  resolveIndex(receiverAst, indexAst, scope, pos, optional) {
12143
- const receiver = this.resolveExpression(receiverAst, scope);
12144
- if (optional && !isNullable(receiver.type)) {
12148
+ const resolvedReceiver = this.resolveExpression(receiverAst, scope);
12149
+ if (optional && !isNullable(resolvedReceiver.type)) {
12145
12150
  throw new CompileError(
12146
- `\`?.[]\` requires an optional receiver; got ${this.describe(receiver.type)}`,
12151
+ `\`?.[]\` requires an optional receiver; got ${this.describe(resolvedReceiver.type)}`,
12152
+ pos
12153
+ );
12154
+ }
12155
+ const receiver = resolvedReceiver.optionalChainType ? { ...resolvedReceiver, type: resolvedReceiver.optionalChainType } : resolvedReceiver;
12156
+ const nullPropagating = optional || resolvedReceiver.optionalChainType !== void 0;
12157
+ if (!optional && isNullable(receiver.type)) {
12158
+ throw new CompileError(
12159
+ `Index access is read on ${this.describe(receiver.type)}, which may be null. Handle null before indexing it \u2014 use \`!\` if it is always set, or \`?.[]\`, \`??\`, or a null check that narrows it.`,
12147
12160
  pos
12148
12161
  );
12149
12162
  }
@@ -12185,9 +12198,10 @@ var init_strict_resolver = __esm({
12185
12198
  pointer: {
12186
12199
  type: "keyOf" /* KeyOf */,
12187
12200
  keyOf: { pointer: receiver.pointer, key: requested.pointer },
12188
- ...optional ? { optional: true } : {}
12201
+ ...nullPropagating ? { optional: true } : {}
12189
12202
  },
12190
- type: optional ? { ...receiver.type.elementType, nullable: true } : receiver.type.elementType,
12203
+ type: nullPropagating ? { ...receiver.type.elementType, nullable: true } : receiver.type.elementType,
12204
+ ...nullPropagating ? { optionalChainType: receiver.type.elementType } : {},
12191
12205
  ...receiver.writability ? { writability: receiver.writability } : {}
12192
12206
  };
12193
12207
  }
@@ -12218,9 +12232,10 @@ var init_strict_resolver = __esm({
12218
12232
  pointer: {
12219
12233
  type: "keyOf" /* KeyOf */,
12220
12234
  keyOf: { pointer: receiver.pointer, key: index.pointer },
12221
- ...optional ? { optional: true } : {}
12235
+ ...nullPropagating ? { optional: true } : {}
12222
12236
  },
12223
- type: optional ? { ...valueType, nullable: true } : valueType,
12237
+ type: nullPropagating ? { ...valueType, nullable: true } : valueType,
12238
+ ...nullPropagating ? { optionalChainType: valueType } : {},
12224
12239
  ...receiver.writability ? { writability: receiver.writability } : {}
12225
12240
  };
12226
12241
  }
@@ -17796,7 +17811,7 @@ var init_project_schema_contract_generated = __esm({
17796
17811
  "../packages/neoscript-language/src/project-schema-contract.generated.ts"() {
17797
17812
  "use strict";
17798
17813
  PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION = 3;
17799
- PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.12";
17814
+ PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.13";
17800
17815
  PROJECT_FILE_UPLOAD_BATCH_SIZE = 32;
17801
17816
  NEO_PROJECT_SOURCE_RECORD_CONTRACT = {
17802
17817
  "recordFields": {
@@ -30758,15 +30773,21 @@ function projectSchemaManifest(input, options = {}) {
30758
30773
  "ProjectSchemaManifest.interfaces"
30759
30774
  );
30760
30775
  const genericNames = /* @__PURE__ */ new Map();
30776
+ const genericDeclarations = /* @__PURE__ */ new Map();
30777
+ const genericOwnerClassIds = /* @__PURE__ */ new Map();
30761
30778
  for (const schemaClass2 of classes) {
30779
+ const classId = string2(schemaClass2.id, "schemaClass.id");
30762
30780
  for (const parameter4 of records(
30763
30781
  schemaClass2.genericParameters ?? [],
30764
30782
  "schemaClass.genericParameters"
30765
30783
  )) {
30784
+ const parameterId = string2(parameter4.id, "genericParameter.id");
30766
30785
  genericNames.set(
30767
- string2(parameter4.id, "genericParameter.id"),
30786
+ parameterId,
30768
30787
  string2(parameter4.name, "genericParameter.name")
30769
30788
  );
30789
+ genericDeclarations.set(parameterId, parameter4);
30790
+ genericOwnerClassIds.set(parameterId, classId);
30770
30791
  }
30771
30792
  }
30772
30793
  const rootMemberIds2 = options.rootMemberIds ?? inferRootMemberIds(members);
@@ -30777,6 +30798,8 @@ function projectSchemaManifest(input, options = {}) {
30777
30798
  interfaces: interfaceById,
30778
30799
  enums: indexById(enums, "ProjectSchemaManifest.enums"),
30779
30800
  genericNames,
30801
+ genericDeclarations,
30802
+ genericOwnerClassIds,
30780
30803
  effectiveWritabilityByMemberId: effectiveMemberWritability(
30781
30804
  members,
30782
30805
  classes,
@@ -31150,7 +31173,7 @@ function schemaClass(value, environment, field) {
31150
31173
  return {
31151
31174
  id: parameterId,
31152
31175
  name,
31153
- type: { kind: "typeParameter", id: parameterId, name },
31176
+ type: typeParameter(parameterId, environment, false),
31154
31177
  ...spanAndSelectionLocations(
31155
31178
  parameter4.source,
31156
31179
  parameter4.selectionSpan ?? parameter4.source,
@@ -32139,14 +32162,73 @@ function manifestPrimitive(kind) {
32139
32162
  return null;
32140
32163
  }
32141
32164
  }
32142
- function typeParameter(id2, environment, nullable) {
32165
+ function typeParameter(id2, environment, nullable, visiting = /* @__PURE__ */ new Set()) {
32166
+ const declaration = environment.genericDeclarations.get(id2);
32167
+ const constraint = declaration?.constraint;
32168
+ const next = new Set(visiting).add(id2);
32143
32169
  return {
32144
32170
  kind: "typeParameter",
32145
32171
  id: id2,
32146
32172
  name: environment.genericNames.get(id2) ?? id2,
32173
+ ...environment.genericOwnerClassIds.has(id2) ? { ownerClassId: environment.genericOwnerClassIds.get(id2) } : {},
32174
+ ...visiting.has(id2) || constraint === void 0 || constraint === null ? {} : {
32175
+ constraint: genericConstraintType(
32176
+ constraint,
32177
+ environment,
32178
+ next,
32179
+ `generic parameter ${id2}.constraint`
32180
+ )
32181
+ },
32147
32182
  nullable
32148
32183
  };
32149
32184
  }
32185
+ function genericConstraintType(value, environment, visiting, field) {
32186
+ const constraint = record2(value, field);
32187
+ if (constraint.kind === "enum") {
32188
+ return {
32189
+ kind: "named",
32190
+ typeId: string2(constraint.enumId, `${field}.enumId`),
32191
+ nullable: false
32192
+ };
32193
+ }
32194
+ if (constraint.kind !== "class") {
32195
+ throw new Error(`${field}.kind must be "class" or "enum".`);
32196
+ }
32197
+ const classId = string2(constraint.classId, `${field}.classId`);
32198
+ const target = environment.classes.get(classId);
32199
+ const parameters = target ? records(target.genericParameters ?? [], `${field}.genericParameters`) : [];
32200
+ const bindings = optionalRecord(constraint.classArguments) ?? {};
32201
+ const typeArguments = parameters.flatMap((parameter4) => {
32202
+ const parameterId = string2(parameter4.id, `${field}.genericParameter.id`);
32203
+ const binding = optionalRecord(bindings[parameterId]);
32204
+ if (binding?.kind === "generic" && typeof binding.genericParamId === "string") {
32205
+ return [
32206
+ typeParameter(binding.genericParamId, environment, false, visiting)
32207
+ ];
32208
+ }
32209
+ if (binding?.kind === "member" && typeof binding.memberId === "string") {
32210
+ const member = environment.members.get(binding.memberId);
32211
+ return member ? [
32212
+ {
32213
+ ...memberType(
32214
+ member,
32215
+ environment,
32216
+ /* @__PURE__ */ new Set(),
32217
+ `${field}.classArguments.${parameterId}`
32218
+ ),
32219
+ nullable: false
32220
+ }
32221
+ ] : [];
32222
+ }
32223
+ return [];
32224
+ });
32225
+ return {
32226
+ kind: "named",
32227
+ typeId: classId,
32228
+ nullable: false,
32229
+ ...typeArguments.length === parameters.length && typeArguments.length > 0 ? { typeArguments } : {}
32230
+ };
32231
+ }
32150
32232
  function primitive(name, nullable) {
32151
32233
  return { kind: "primitive", name, nullable };
32152
32234
  }
@@ -34673,6 +34755,16 @@ function inferMemberOwners(classes, members) {
34673
34755
  schemaClass2.recordId,
34674
34756
  set
34675
34757
  );
34758
+ if (Array.isArray(schemaClass2.data.genericParams)) {
34759
+ for (const parameterValue of schemaClass2.data.genericParams) {
34760
+ if (!isRecord2(parameterValue)) continue;
34761
+ const parameterId = parameterValue.id;
34762
+ const constraint = parameterValue.constraint;
34763
+ if (typeof parameterId !== "string" || !isRecord2(constraint)) continue;
34764
+ if (constraint.kind !== "class") continue;
34765
+ collectGenericOwners(constraint.classArguments, parameterId, set);
34766
+ }
34767
+ }
34676
34768
  }
34677
34769
  for (const [memberId, member] of members) {
34678
34770
  if (typeof member.entryMemberId === "string") {
@@ -38073,8 +38165,19 @@ function assertGenericParameter(value, path) {
38073
38165
  if (parameter4.constraint === null) return;
38074
38166
  const constraint = requireRecord(parameter4.constraint, `${path}.constraint`);
38075
38167
  if (constraint.kind === "class") {
38076
- knownKeys(constraint, `${path}.constraint`, ["kind", "classId"]);
38168
+ knownKeysWithOptional(
38169
+ constraint,
38170
+ `${path}.constraint`,
38171
+ ["kind", "classId"],
38172
+ ["classArguments"]
38173
+ );
38077
38174
  nonEmptyString(constraint.classId, `${path}.constraint.classId`);
38175
+ if (constraint.classArguments !== void 0 && constraint.classArguments !== null) {
38176
+ assertGenericBindingRecord(
38177
+ constraint.classArguments,
38178
+ `${path}.constraint.classArguments`
38179
+ );
38180
+ }
38078
38181
  return;
38079
38182
  }
38080
38183
  if (constraint.kind === "enum") {
@@ -41812,7 +41915,9 @@ function isGenericParamConstraint(value) {
41812
41915
  if (v.kind === "class") {
41813
41916
  if (typeof v.classId !== "string") return false;
41814
41917
  if (v.classId.length === 0) return false;
41815
- return v.enumId === void 0;
41918
+ if (v.enumId !== void 0) return false;
41919
+ const argumentsValue = v.classArguments;
41920
+ return argumentsValue === void 0 || argumentsValue === null || isGenericBindingsRecord(argumentsValue);
41816
41921
  }
41817
41922
  if (v.kind === "enum") {
41818
41923
  if (typeof v.enumId !== "string") return false;
@@ -49582,11 +49687,7 @@ function emitClass(context, schemaClass2, memberIds) {
49582
49687
  const modifier = schemaClass2.isSealed ? "sealed " : schemaClass2.declarationModifier === "abstract" ? "abstract " : "";
49583
49688
  const generics = schemaClass2.genericParameters.length ? `<
49584
49689
  ${schemaClass2.genericParameters.map((parameter4) => {
49585
- const constraint = parameter4.constraint ? ` extends ${parameter4.constraint.kind === "class" ? required(
49586
- context.classes,
49587
- parameter4.constraint.classId,
49588
- "class"
49589
- ).name : required(context.enums, parameter4.constraint.enumId, "enum").name}` : "";
49690
+ const constraint = parameter4.constraint ? ` extends ${renderGenericConstraint(context, parameter4.constraint)}` : "";
49590
49691
  return ` ${id(parameter4.id)} ${parameter4.name}${constraint}`;
49591
49692
  }).join(",\n")}
49592
49693
  >` : "";
@@ -49660,6 +49761,31 @@ ${members.join("\n\n")}
49660
49761
  }
49661
49762
  `;
49662
49763
  }
49764
+ function renderGenericConstraint(context, constraint) {
49765
+ if (constraint.kind === "enum") {
49766
+ return required(context.enums, constraint.enumId, "enum").name;
49767
+ }
49768
+ const target = required(context.classes, constraint.classId, "class");
49769
+ if (target.genericParameters.length === 0) return target.name;
49770
+ const bindings = constraint.classArguments;
49771
+ if (bindings === void 0 || bindings === null) return target.name;
49772
+ const argumentsValue = target.genericParameters.map((parameter4) => {
49773
+ const binding = bindings[parameter4.id];
49774
+ if (binding === void 0) {
49775
+ throw new Error(
49776
+ `Generic constraint ${target.name} is missing its argument for ${parameter4.name}.`
49777
+ );
49778
+ }
49779
+ if (binding.kind === "generic") {
49780
+ return genericName(context, binding.genericParamId);
49781
+ }
49782
+ return renderMemberType(
49783
+ context,
49784
+ required(context.members, binding.memberId, "generic constraint binding")
49785
+ ).replace(/\?$/u, "");
49786
+ });
49787
+ return `${target.name}<${argumentsValue.join(", ")}>`;
49788
+ }
49663
49789
  function baseConstruction(requiredConstructor) {
49664
49790
  if (requiredConstructor === void 0) return "";
49665
49791
  const args = requiredConstructor.baseArguments ?? [];
@@ -56139,7 +56265,15 @@ function lowerClass(context, declaration) {
56139
56265
  return {
56140
56266
  id: parameterId,
56141
56267
  name: parameter4.name,
56142
- constraint: parameter4.constraint ? genericConstraint(context, parameter4.constraint) : null,
56268
+ constraint: parameter4.constraint ? genericConstraint(
56269
+ context,
56270
+ declaration,
56271
+ parameterId,
56272
+ parameter4.constraint,
56273
+ base?.genericParameters.find(
56274
+ (candidate) => candidate.id === parameterId
56275
+ )?.constraint
56276
+ ) : null,
56143
56277
  source: span(parameter4.identity),
56144
56278
  selectionSpan: span(parameter4.identity)
56145
56279
  };
@@ -58090,9 +58224,22 @@ function lowerGenericArgument(context, ownerClass, type, memberId, genericParamI
58090
58224
  )
58091
58225
  );
58092
58226
  }
58093
- function genericConstraint(context, type) {
58227
+ function genericConstraint(context, ownerClass, parameterId, type, baseConstraint) {
58094
58228
  const classId = context.classIdsByName.get(type.name);
58095
- if (classId) return { kind: "class", classId };
58229
+ if (classId) {
58230
+ const classArguments2 = type.arguments.length === 0 ? void 0 : lowerClassArguments(
58231
+ context,
58232
+ ownerClass,
58233
+ parameterId,
58234
+ type,
58235
+ baseConstraint?.kind === "class" ? baseConstraint.classArguments ?? void 0 : void 0
58236
+ );
58237
+ return {
58238
+ kind: "class",
58239
+ classId,
58240
+ ...classArguments2 === void 0 ? {} : { classArguments: classArguments2 }
58241
+ };
58242
+ }
58096
58243
  return {
58097
58244
  kind: "enum",
58098
58245
  enumId: requiredName2(context.enumIdsByName, type.name, "enum")
@@ -58998,18 +59145,12 @@ function classToLanguageType(schemaClass2, context) {
58998
59145
  typeParameters: schemaClass2.genericParams.map((parameter4) => ({
58999
59146
  id: parameter4.id,
59000
59147
  name: parameter4.name,
59001
- type: {
59002
- kind: "typeParameter",
59003
- id: parameter4.id,
59004
- name: parameter4.name,
59005
- ownerClassId: schemaClass2.id,
59006
- ...parameter4.constraint === void 0 || parameter4.constraint === null ? {} : {
59007
- constraint: namedType(
59008
- parameter4.constraint.kind === "class" ? parameter4.constraint.classId : parameter4.constraint.enumId,
59009
- true
59010
- )
59011
- }
59012
- },
59148
+ type: genericParameterLanguageType(
59149
+ parameter4.id,
59150
+ true,
59151
+ context,
59152
+ genericEnvironment
59153
+ ),
59013
59154
  location: virtualIdentifierLocation(
59014
59155
  context,
59015
59156
  "Classes",
@@ -59659,31 +59800,12 @@ function memberRuntimeType(record3, context, seen = /* @__PURE__ */ new Set(), g
59659
59800
  );
59660
59801
  }
59661
59802
  }
59662
- for (const owner of context.vm.classes) {
59663
- const parameter4 = owner.genericParams?.find(
59664
- (candidate) => candidate.id === genericParamId
59665
- );
59666
- if (parameter4) {
59667
- if (parameter4.constraint?.kind === "class") {
59668
- return namedType(parameter4.constraint.classId, required2);
59669
- }
59670
- if (parameter4.constraint?.kind === "enum") {
59671
- return namedType(parameter4.constraint.enumId, required2);
59672
- }
59673
- return {
59674
- kind: "typeParameter",
59675
- id: genericParamId,
59676
- name: parameter4.name,
59677
- nullable: !required2
59678
- };
59679
- }
59680
- }
59681
- return {
59682
- kind: "typeParameter",
59683
- id: genericParamId,
59684
- name: genericParamId,
59685
- nullable: !required2
59686
- };
59803
+ return genericParameterLanguageType(
59804
+ genericParamId,
59805
+ required2,
59806
+ context,
59807
+ genericEnvironment
59808
+ );
59687
59809
  }
59688
59810
  case 27 /* Variant */: {
59689
59811
  if (!isMemberVariantBase(member)) return UNKNOWN_TYPE2;
@@ -59746,13 +59868,13 @@ function classArguments(member, context, seen, genericEnvironment) {
59746
59868
  )
59747
59869
  ] : [];
59748
59870
  }
59749
- const declaration = context.vm.classes.flatMap((schemaClass2) => schemaClass2.genericParams ?? []).find((candidate) => candidate.id === binding.genericParamId);
59750
59871
  return [
59751
- {
59752
- kind: "typeParameter",
59753
- id: binding.genericParamId,
59754
- name: declaration?.name ?? binding.genericParamId
59755
- }
59872
+ genericParameterLanguageType(
59873
+ binding.genericParamId,
59874
+ true,
59875
+ context,
59876
+ genericEnvironment
59877
+ )
59756
59878
  ];
59757
59879
  }
59758
59880
  const argument2 = analyzerMemberById(context.vm, binding.memberId);
@@ -59900,33 +60022,89 @@ function toLanguageType(type, context, genericEnvironment) {
59900
60022
  );
59901
60023
  }
59902
60024
  }
59903
- const effectiveParamId = environmentEntry?.kind === "unbound" ? environmentEntry.paramId : type.genericParamId;
59904
- const owner = context.vm.classes.find(
59905
- (item) => item.id === type.ownerClassId || item.genericParams?.some(
59906
- (parameter4) => parameter4.id === effectiveParamId
59907
- )
60025
+ return genericParameterLanguageType(
60026
+ environmentEntry?.kind === "unbound" ? environmentEntry.paramId : type.genericParamId,
60027
+ required2,
60028
+ context,
60029
+ genericEnvironment
59908
60030
  );
59909
- const name = owner?.genericParams?.find(
59910
- (parameter4) => parameter4.id === effectiveParamId
59911
- )?.name ?? effectiveParamId;
59912
- const declaration = owner?.genericParams?.find(
59913
- (parameter4) => parameter4.id === effectiveParamId
60031
+ }
60032
+ }
60033
+ }
60034
+ function genericParameterLanguageType(genericParamId, required2, context, genericEnvironment, visiting = /* @__PURE__ */ new Set()) {
60035
+ const environmentEntry = genericEnvironment?.get(genericParamId);
60036
+ if (environmentEntry?.kind === "member") {
60037
+ const binding = analyzerMemberById(context.vm, environmentEntry.memberId);
60038
+ if (binding) {
60039
+ return requiredType(
60040
+ memberRuntimeType(binding, context, /* @__PURE__ */ new Set(), genericEnvironment)
59914
60041
  );
59915
- return {
59916
- kind: "typeParameter",
59917
- id: effectiveParamId,
59918
- name,
59919
- ...owner === void 0 ? {} : { ownerClassId: owner.id },
59920
- ...declaration?.constraint === void 0 || declaration.constraint === null ? {} : {
59921
- constraint: namedType(
59922
- declaration.constraint.kind === "class" ? declaration.constraint.classId : declaration.constraint.enumId,
59923
- true
59924
- )
59925
- },
59926
- nullable: !required2
59927
- };
59928
60042
  }
59929
60043
  }
60044
+ const effectiveParamId = environmentEntry?.kind === "unbound" ? environmentEntry.paramId : genericParamId;
60045
+ const owner = context.vm.classes.find(
60046
+ (schemaClass2) => schemaClass2.genericParams?.some(
60047
+ (parameter4) => parameter4.id === effectiveParamId
60048
+ )
60049
+ );
60050
+ const declaration = owner?.genericParams?.find(
60051
+ (parameter4) => parameter4.id === effectiveParamId
60052
+ );
60053
+ const next = new Set(visiting).add(effectiveParamId);
60054
+ return {
60055
+ kind: "typeParameter",
60056
+ id: effectiveParamId,
60057
+ name: declaration?.name ?? effectiveParamId,
60058
+ ...owner === void 0 ? {} : { ownerClassId: owner.id },
60059
+ ...visiting.has(effectiveParamId) || declaration?.constraint == null ? {} : {
60060
+ constraint: genericConstraintLanguageType(
60061
+ declaration.constraint,
60062
+ context,
60063
+ genericEnvironment,
60064
+ next
60065
+ )
60066
+ },
60067
+ nullable: !required2
60068
+ };
60069
+ }
60070
+ function genericConstraintLanguageType(constraint, context, genericEnvironment, visiting = /* @__PURE__ */ new Set()) {
60071
+ if (constraint.kind === "enum") {
60072
+ return namedType(constraint.enumId, true);
60073
+ }
60074
+ const target = context.vm.classes.find(
60075
+ (schemaClass2) => schemaClass2.id === constraint.classId
60076
+ );
60077
+ const bindings = constraint.classArguments;
60078
+ if (!target?.genericParams?.length || bindings == null) {
60079
+ return namedType(constraint.classId, true);
60080
+ }
60081
+ const typeArguments = target.genericParams.flatMap((parameter4) => {
60082
+ const binding = bindings[parameter4.id];
60083
+ if (binding === void 0) return [];
60084
+ if (binding.kind === "generic") {
60085
+ return [
60086
+ genericParameterLanguageType(
60087
+ binding.genericParamId,
60088
+ true,
60089
+ context,
60090
+ genericEnvironment,
60091
+ visiting
60092
+ )
60093
+ ];
60094
+ }
60095
+ const member = analyzerMemberById(context.vm, binding.memberId);
60096
+ return member ? [
60097
+ requiredType(
60098
+ memberRuntimeType(member, context, /* @__PURE__ */ new Set(), genericEnvironment)
60099
+ )
60100
+ ] : [];
60101
+ });
60102
+ return {
60103
+ kind: "named",
60104
+ typeId: constraint.classId,
60105
+ nullable: false,
60106
+ ...typeArguments.length === target.genericParams.length ? { typeArguments } : {}
60107
+ };
59930
60108
  }
59931
60109
  function functionReturnType(type, context, genericEnvironment) {
59932
60110
  return type.type === NS_TYPE_VOID ? primitive2("void", true) : toLanguageType(type, context, genericEnvironment);
@@ -60094,12 +60272,68 @@ function virtualNeoSchemaClassHeader(schemaClass2, context) {
60094
60272
  )?.name ?? constraint.classId : context.vm.enums.find(
60095
60273
  (candidate) => candidate.id === constraint.enumId
60096
60274
  )?.name ?? constraint.enumId;
60275
+ const targetClass = constraint.kind === "class" ? context.vm.classes.find(
60276
+ (candidate) => candidate.id === constraint.classId
60277
+ ) : void 0;
60278
+ const bindings = constraint.kind === "class" ? constraint.classArguments : void 0;
60279
+ const argumentsValue = targetClass?.genericParams?.flatMap(
60280
+ (targetParameter) => {
60281
+ const binding = bindings?.[targetParameter.id];
60282
+ if (binding?.kind === "generic") {
60283
+ const declaration = context.vm.classes.flatMap((candidate) => candidate.genericParams ?? []).find((candidate) => candidate.id === binding.genericParamId);
60284
+ return [
60285
+ virtualCSharpIdentifier(
60286
+ declaration?.name ?? binding.genericParamId
60287
+ )
60288
+ ];
60289
+ }
60290
+ if (binding?.kind === "member") {
60291
+ const member = analyzerMemberById(context.vm, binding.memberId);
60292
+ return [
60293
+ member ? virtualConstraintMemberType(member, context) : "object"
60294
+ ];
60295
+ }
60296
+ return [];
60297
+ }
60298
+ );
60299
+ const argumentSuffix = targetClass?.genericParams?.length && argumentsValue?.length === targetClass.genericParams.length ? `<${argumentsValue.join(", ")}>` : "";
60097
60300
  return [
60098
- ` where ${virtualCSharpIdentifier(parameter4.name)} : ${virtualCSharpIdentifier(target)}`
60301
+ ` where ${virtualCSharpIdentifier(parameter4.name)} : ${virtualCSharpIdentifier(target)}${argumentSuffix}`
60099
60302
  ];
60100
60303
  });
60101
60304
  return `public ${schemaClass2.isAbstract ? "abstract " : ""}partial class ${virtualCSharpIdentifier(schemaClass2.name)}${genericParameters}${inheritance}${constraints.join("")} // Neo class ${schemaClass2.id}`;
60102
60305
  }
60306
+ function virtualConstraintMemberType(member, context) {
60307
+ const resolved = resolveMember2(member, context.vm.members);
60308
+ switch (resolved.kind) {
60309
+ case 1 /* Bool */:
60310
+ return "bool";
60311
+ case 2 /* Int */:
60312
+ return "int";
60313
+ case 3 /* String */:
60314
+ return "string";
60315
+ case 4 /* Float */:
60316
+ return "double";
60317
+ case 20 /* Decimal */:
60318
+ return "decimal";
60319
+ case 7 /* Class */: {
60320
+ const classId = getOptionalString(resolved, "classId");
60321
+ const schemaClass2 = context.vm.classes.find(
60322
+ (candidate) => candidate.id === classId
60323
+ );
60324
+ return virtualCSharpIdentifier(schemaClass2?.name ?? classId ?? "object");
60325
+ }
60326
+ case 8 /* Enum */: {
60327
+ const enumId = getOptionalString(resolved, "enumId");
60328
+ const neoEnum = context.vm.enums.find(
60329
+ (candidate) => candidate.id === enumId
60330
+ );
60331
+ return virtualCSharpIdentifier(neoEnum?.name ?? enumId ?? "object");
60332
+ }
60333
+ default:
60334
+ return "object";
60335
+ }
60336
+ }
60103
60337
  function virtualNeoSchemaClassMemberLine(schemaKey, displayName, memberId) {
60104
60338
  return ` public object? ${virtualCSharpIdentifier(schemaKey)} { get; init; } // ${displayName}; Neo member ${memberId}`;
60105
60339
  }
@@ -60135,7 +60369,7 @@ var init_neoscript_language_context_adapter = __esm({
60135
60369
  init_project_root_members();
60136
60370
  init_project_file_registry();
60137
60371
  init_analyzer_types();
60138
- NEOSCRIPT_COMPILER_ADAPTER_REVISION = 1;
60372
+ NEOSCRIPT_COMPILER_ADAPTER_REVISION = 2;
60139
60373
  UNKNOWN_TYPE2 = {
60140
60374
  kind: "primitive",
60141
60375
  name: "unknown"
@@ -82237,10 +82471,13 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
82237
82471
  `Class "${schemaClass2.name}" (${schemaClass2.id}) cannot extend sealed class "${baseClass.name}" (${baseClass.id}).`
82238
82472
  );
82239
82473
  }
82240
- const genericParamConstraintsById = new Map(
82474
+ const genericParamDeclarationsById = new Map(
82241
82475
  classes.flatMap(
82242
82476
  (schemaClass2) => (schemaClass2.genericParams ?? []).map(
82243
- (param) => [param.id, param.constraint]
82477
+ (param) => [
82478
+ param.id,
82479
+ { constraint: param.constraint, ownerClassId: schemaClass2.id }
82480
+ ]
82244
82481
  )
82245
82482
  )
82246
82483
  );
@@ -83155,13 +83392,40 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
83155
83392
  if (!isRecord7(typeInfo) || typeInfo.type !== 21 || typeof typeInfo.genericParamId !== "string") {
83156
83393
  return null;
83157
83394
  }
83158
- const constraint = genericParamConstraintsById.get(typeInfo.genericParamId);
83395
+ const constraint = genericParamDeclarationsById.get(
83396
+ typeInfo.genericParamId
83397
+ )?.constraint;
83159
83398
  if (!constraint) return null;
83160
- return constraint.kind === "class" ? {
83161
- type: 7,
83162
- required: typeInfo.required === true,
83163
- classId: constraint.classId
83164
- } : {
83399
+ if (constraint.kind === "class") {
83400
+ const typeArguments = Object.fromEntries(
83401
+ Object.entries(constraint.classArguments ?? {}).flatMap(
83402
+ ([parameterId, binding]) => {
83403
+ if (binding.kind !== "generic") return [];
83404
+ const ownerClassId = genericParamDeclarationsById.get(
83405
+ binding.genericParamId
83406
+ )?.ownerClassId;
83407
+ return ownerClassId === void 0 ? [] : [
83408
+ [
83409
+ parameterId,
83410
+ {
83411
+ type: 21,
83412
+ required: true,
83413
+ ownerClassId,
83414
+ genericParamId: binding.genericParamId
83415
+ }
83416
+ ]
83417
+ ];
83418
+ }
83419
+ )
83420
+ );
83421
+ return {
83422
+ type: 7,
83423
+ required: typeInfo.required === true,
83424
+ classId: constraint.classId,
83425
+ ...Object.keys(typeArguments).length > 0 ? { typeArguments } : {}
83426
+ };
83427
+ }
83428
+ return {
83165
83429
  type: 8,
83166
83430
  required: typeInfo.required === true,
83167
83431
  enumId: constraint.enumId
@@ -111011,8 +111275,8 @@ var init_registry2 = __esm({
111011
111275
  "use strict";
111012
111276
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111013
111277
  formatVersion: 3,
111014
- contractVersion: "3.12",
111015
- cliVersion: "0.31.6",
111278
+ contractVersion: "3.13",
111279
+ cliVersion: "0.31.8",
111016
111280
  projectFileUploadBatchSize: 32,
111017
111281
  documentRecords: {
111018
111282
  member: {
@@ -117607,7 +117871,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
117607
117871
  async function main() {
117608
117872
  const args = parseArgs(process.argv.slice(2));
117609
117873
  if (args.command === "--version") {
117610
- console.log("0.31.6");
117874
+ console.log("0.31.8");
117611
117875
  return;
117612
117876
  }
117613
117877
  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.31.6",
3
+ "version": "0.31.8",
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.31.6 -->
12
+ <!-- reviewed-through-cli: 0.31.8 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.31.6 -->
86
+ <!-- reviewed-through-cli: 0.31.8 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -287,3 +287,16 @@ class Child(string name) : Base {
287
287
  Bind generic base arguments explicitly when needed. Reject a subclass that
288
288
  omits required base arguments or leaves any inherited required member
289
289
  unsettled.
290
+
291
+ Generic constraints may construct another generic from a parameter in the
292
+ same declaration. This preserves the relationship between the parameters in
293
+ source checks, stored initializer replay, and generated C#:
294
+
295
+ ```neo
296
+ class PlacedItem<
297
+ TData extends InventoryStackData,
298
+ TItem extends InventoryItem<TData>
299
+ >(TItem item) {
300
+ public TData Data = item.DefaultData.Clone();
301
+ }
302
+ ```
@@ -49,6 +49,11 @@ if (player.Target != null) {
49
49
  return player.Target?.Name ?? "Unknown";
50
50
  ```
51
51
 
52
+ Null propagation remains active across following required accesses in the same
53
+ chain, so `config.Hat?.Head.Idle.Down.Frames` safely returns null when `Hat` is
54
+ null. A descendant that is itself nullable still needs its own `?.`, `??`, `!`,
55
+ or narrowing guard before the next access.
56
+
52
57
  Treat lookup-return values with the same nullable narrowing rules.
53
58
 
54
59
  Let runtime ownership—not body kind—decide whether a write target is Immutable,