@neocompose/cli 0.72.1 → 0.72.2

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.72.2] - 2026-10-08
4
+
5
+ - Resolve void statement calls through generic class and interface constraints, preserving generic arguments and receiver nullability checks.
6
+ - Infer contextual enum literals in positional and named constructor arguments, including generic parameters and overloads with defaults.
7
+ - Reject unrelated explicit class constructions in typed initializers, with source diagnostics that agree between the editor and CLI. Preserve concrete and generic types when a local target-typed construction is stored through a base-class member.
8
+
3
9
  ## [0.72.1] - 2026-10-07
4
10
 
5
11
  - Speed up `neo export` and `neo dev`: reuse verified unchanged exports, cache unchanged parsing and preparation work, share dialogue compiler symbols, and index Unity generation. Regenerate C# only when its schema inputs change; continue validating edited content and repairing changed output files.
package/dist/neo.mjs CHANGED
@@ -20567,11 +20567,12 @@ var init_local_values = __esm({
20567
20567
  init_formatter();
20568
20568
  init_workspace_source_path();
20569
20569
  init_executable_parser();
20570
+ init_source_text();
20570
20571
  init_language_spec();
20571
20572
  init_lower_support();
20572
20573
  LocalValueBindingsV4 = class {
20573
20574
  definitions = /* @__PURE__ */ new Map();
20574
- expressions = /* @__PURE__ */ new Map();
20575
+ initializers = /* @__PURE__ */ new Map();
20575
20576
  owners = /* @__PURE__ */ new Map();
20576
20577
  /** Static members whose initializer is a bare local name, by that name. */
20577
20578
  ownerMembers = /* @__PURE__ */ new Map();
@@ -20608,7 +20609,7 @@ var init_local_values = __esm({
20608
20609
  owners.set(valueId, source.label);
20609
20610
  this.owners.set(global.name, owners);
20610
20611
  }
20611
- text = global.initializer;
20612
+ text = step.text;
20612
20613
  expression = step.expression;
20613
20614
  source = {
20614
20615
  ...source,
@@ -20627,7 +20628,28 @@ var init_local_values = __esm({
20627
20628
  let last = null;
20628
20629
  for (const step of this.chain(name)) last = step;
20629
20630
  if (last === null) return null;
20630
- return { expression: last.expression, text: last.global.initializer };
20631
+ return { expression: last.expression, text: last.text };
20632
+ }
20633
+ initializer(global) {
20634
+ const cached = this.initializers.get(global.name);
20635
+ if (cached) return cached;
20636
+ let text = global.initializer;
20637
+ let expression = parseExpression(text);
20638
+ let construction = expression;
20639
+ while (construction.kind === "annotated")
20640
+ construction = construction.expression;
20641
+ if (construction.kind === "new" && construction.className === null) {
20642
+ const offset = new SourceText(text).offsetAt({
20643
+ line: construction.pos.line - 1,
20644
+ character: construction.pos.column - 1
20645
+ }) + 3;
20646
+ const type = typeText({ ...global.type, nullable: false });
20647
+ text = `${text.slice(0, offset)} ${type}${text.slice(offset)}`;
20648
+ expression = parseExpression(text);
20649
+ }
20650
+ const prepared = { global, expression, text };
20651
+ this.initializers.set(global.name, prepared);
20652
+ return prepared;
20631
20653
  }
20632
20654
  /** Each local an alias chain starting at `name` passes through, in order. */
20633
20655
  *chain(name) {
@@ -20637,9 +20659,9 @@ var init_local_values = __esm({
20637
20659
  if (visited.has(global.name))
20638
20660
  throw new Error(`Local initializer cycle reaches ${global.name}.`);
20639
20661
  visited.add(global.name);
20640
- const expression = this.expressions.get(global.name) ?? parseExpression(global.initializer);
20641
- this.expressions.set(global.name, expression);
20642
- yield { global, expression };
20662
+ const prepared = this.initializer(global);
20663
+ const { expression } = prepared;
20664
+ yield prepared;
20643
20665
  global = expression.kind === "ident" ? this.definitions.get(expression.name) : void 0;
20644
20666
  }
20645
20667
  }
@@ -20685,7 +20707,7 @@ var init_local_values = __esm({
20685
20707
  const placements = [];
20686
20708
  for (const [name, definition2] of this.definitions) {
20687
20709
  const valueIds = this.owners.get(name)?.keys().toArray() ?? [];
20688
- const expression = this.expressions.get(name) ?? parseExpression(definition2.initializer);
20710
+ const expression = this.initializers.get(name)?.expression ?? parseExpression(definition2.initializer);
20689
20711
  placements.push({
20690
20712
  name,
20691
20713
  type: typeText(definition2.type),
@@ -28282,6 +28304,20 @@ var init_collection_query = __esm({
28282
28304
  function neoScriptArgumentNameSetKey(names) {
28283
28305
  return [...names].map((name) => name.toLowerCase()).sort().join(",");
28284
28306
  }
28307
+ function neoScriptParameterByName(parameters, name) {
28308
+ let indexes3 = parameterNameIndexes.get(parameters);
28309
+ if (!indexes3) {
28310
+ const built = /* @__PURE__ */ new Map();
28311
+ for (let index2 = 0; index2 < parameters.length; index2++) {
28312
+ const key = parameters[index2].name.toLowerCase();
28313
+ if (!built.has(key)) built.set(key, index2);
28314
+ }
28315
+ indexes3 = built;
28316
+ parameterNameIndexes.set(parameters, indexes3);
28317
+ }
28318
+ const index = indexes3.get(name.toLowerCase());
28319
+ return index === void 0 ? void 0 : parameters[index];
28320
+ }
28285
28321
  function neoScriptCallMatchesParameters(suppliedNames, parameters) {
28286
28322
  const supplied = new Set(suppliedNames.map((name) => name.toLowerCase()));
28287
28323
  const declared = new Set(
@@ -28542,10 +28578,12 @@ function describeParameter(parameter4) {
28542
28578
  const defaulted = parameter4.defaultValue === void 0 ? "" : ` = ${parameter4.defaultValue.displayText}`;
28543
28579
  return `${parameter4.name}${nullability}${defaulted}`;
28544
28580
  }
28581
+ var parameterNameIndexes;
28545
28582
  var init_declared_constructors = __esm({
28546
28583
  "../packages/neoscript-language/src/declared-constructors.ts"() {
28547
28584
  "use strict";
28548
28585
  init_project2();
28586
+ parameterNameIndexes = /* @__PURE__ */ new WeakMap();
28549
28587
  }
28550
28588
  });
28551
28589
 
@@ -35304,6 +35342,7 @@ var init_strict_resolver = __esm({
35304
35342
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === callee.name
35305
35343
  );
35306
35344
  if (member) {
35345
+ this.validateFunctionReceiver(receiver, callee.name, optional2, pos);
35307
35346
  if (type !== void 0) this.assertMemberAccessible(type, member, pos);
35308
35347
  if (member.static === true) {
35309
35348
  throw new CompileError(
@@ -36452,12 +36491,10 @@ var init_strict_resolver = __esm({
36452
36491
  callableSignatureForReceiver(symbol, receiver) {
36453
36492
  let parameters = symbol.parameters ?? [];
36454
36493
  let returnType = symbol.returnType ?? symbol.type;
36455
- if (this.context.projectSourceKind !== "spec" && !(receiver?.kind === "instance" && receiver.dispatch === "base")) {
36456
- return { parameters, returnType };
36457
- }
36458
36494
  if (receiver?.kind !== "instance") return { parameters, returnType };
36459
36495
  const receiverType = receiver.expression.type;
36460
36496
  if (receiverType.kind !== "named") return { parameters, returnType };
36497
+ if (!receiverType.typeArguments?.length) return { parameters, returnType };
36461
36498
  const declaring = this.project.typeById.get(receiverType.typeId);
36462
36499
  if (declaring === void 0) return { parameters, returnType };
36463
36500
  parameters = parameters.map((parameter4) => ({
@@ -37361,6 +37398,21 @@ var init_strict_resolver = __esm({
37361
37398
  callSiteId: this.nextCallSite(expression.pos)
37362
37399
  };
37363
37400
  }
37401
+ validateFunctionReceiver(receiver, name, optional2, pos) {
37402
+ if (optional2 && !isNullable(receiver.type)) {
37403
+ throw new CompileError(
37404
+ `\`?.\` requires an optional receiver; got ${this.describe(receiver.type)}`,
37405
+ pos
37406
+ );
37407
+ }
37408
+ const receiverType = receiver.optionalChainType ?? receiver.type;
37409
+ if (!optional2 && isNullable(receiverType)) {
37410
+ throw new CompileError(
37411
+ `Function '${name}' is called on ${this.describe(receiverType)}, which may be null. Use optional chaining, force-unwrapping, or a null check before calling it.`,
37412
+ pos
37413
+ );
37414
+ }
37415
+ }
37364
37416
  resolveFunctionStatement(expression, scope) {
37365
37417
  if (expression.kind !== "call") return null;
37366
37418
  const receiverValue = this.callReceiver(expression.callee, scope);
@@ -37524,12 +37576,19 @@ var init_strict_resolver = __esm({
37524
37576
  }
37525
37577
  }
37526
37578
  const receiver = receiverValue();
37527
- if (receiver.type.kind !== "named") return null;
37528
- const type = this.project.typeById.get(receiver.type.typeId);
37579
+ const memberReceiverType = neoScriptMemberLookupType(receiver.type);
37580
+ if (memberReceiverType.kind !== "named") return null;
37581
+ const type = this.project.typeById.get(memberReceiverType.typeId);
37529
37582
  const symbol = type?.members.find(
37530
37583
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === memberName
37531
37584
  );
37532
37585
  if (!symbol) return null;
37586
+ this.validateFunctionReceiver(
37587
+ receiver,
37588
+ memberName,
37589
+ expression.callee.optional === true,
37590
+ expression.pos
37591
+ );
37533
37592
  if (type !== void 0) {
37534
37593
  this.assertMemberAccessible(type, symbol, expression.pos);
37535
37594
  }
@@ -37542,7 +37601,10 @@ var init_strict_resolver = __esm({
37542
37601
  this.validateCallableInContext(symbol, expression.pos);
37543
37602
  const resolved = this.resolveCallableArguments(
37544
37603
  symbol,
37545
- { kind: "instance", expression: receiver },
37604
+ {
37605
+ kind: "instance",
37606
+ expression: memberReceiverType === receiver.type ? receiver : { ...receiver, type: memberReceiverType }
37607
+ },
37546
37608
  expression.args,
37547
37609
  scope,
37548
37610
  expression.pos
@@ -42576,6 +42638,7 @@ function createEnvironment(documents, project) {
42576
42638
  project,
42577
42639
  projectIndex: createProjectIndex(project),
42578
42640
  listenerResolvers: /* @__PURE__ */ new Map(),
42641
+ assignability: /* @__PURE__ */ new Map(),
42579
42642
  enums,
42580
42643
  types,
42581
42644
  globals,
@@ -43854,13 +43917,19 @@ function validateExpression(expression, expected, scope, environment, uri, range
43854
43917
  );
43855
43918
  return;
43856
43919
  }
43857
- if (expected && expression.kind !== "contextualEnum" && !isReferenceCall(expression)) {
43920
+ if (expected && expression.kind !== "contextualEnum" && expression.kind !== "annotated" && !isReferenceCall(expression)) {
43858
43921
  const actual = inferExpressionType(expression, scope, environment);
43859
- if (actual && !semanticTypesAssignable(actual, expected)) {
43922
+ const explicitConstruction = expression.kind === "new" && expression.className !== null && expected.name !== "Partial";
43923
+ const assignable = actual === void 0 || (explicitConstruction ? constructionTypesAssignable(actual, expected, scope, environment) : semanticTypesAssignable(actual, expected));
43924
+ if (actual && !assignable) {
43860
43925
  pushDiagnostic(
43861
43926
  diagnostics,
43862
43927
  uri,
43863
- anchor ? anchorSpan(anchor, expression.pos, 1) : range2,
43928
+ anchor ? anchorSpan(
43929
+ anchor,
43930
+ expression.pos,
43931
+ expression.kind === "new" ? 3 : 1
43932
+ ) : range2,
43864
43933
  "project-expression-type-mismatch",
43865
43934
  `Expected ${describeType(expected)} but received ${describeType(actual)}.`
43866
43935
  );
@@ -44134,23 +44203,26 @@ function validateExpression(expression, expected, scope, environment, uri, range
44134
44203
  }))
44135
44204
  );
44136
44205
  }
44137
- const fields = declaration?.members.filter(
44138
- (member) => member.kind === "field" && !member.modifiers.includes("static")
44139
- ) ?? [];
44140
- const overload = declaration && declaration.constructors.length > 0 ? matchingDeclaredConstructor(declaration, expression.argumentNames) : null;
44206
+ const parameters = declaration.headerParameters ?? matchingDeclaredConstructor(declaration, siteArgumentNames)?.parameters;
44207
+ const fields = [];
44208
+ const fieldsByName = /* @__PURE__ */ new Map();
44209
+ if (declaration.headerParameters === void 0 && declaration.constructors.length === 0) {
44210
+ for (const member of declaration.members) {
44211
+ if (member.kind !== "field" || member.modifiers.includes("static"))
44212
+ continue;
44213
+ fields.push(member);
44214
+ fieldsByName.set(member.name, member);
44215
+ }
44216
+ }
44141
44217
  expression.args.forEach((argument2, index) => {
44142
44218
  const authoredName = expression.argumentNames?.[index];
44143
44219
  const named = declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME ? authoredName ?? NEO_LOOKUP_VARIANT_FOLDER_CONSTRUCTOR_ARGUMENTS[index]?.name : authoredName;
44144
44220
  if (declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME && named === "collection") {
44145
44221
  return;
44146
44222
  }
44147
- const parameter4 = named ? (declaration.headerParameters ?? []).find(
44148
- (candidate) => candidate.name === named
44149
- ) ?? overload?.parameters.find((candidate) => candidate.name === named) : void 0;
44223
+ const parameter4 = named && parameters ? neoScriptParameterByName(parameters, named) : parameters?.[index];
44150
44224
  const variantMember = named === void 0 || named === null ? void 0 : variantSite === null ? void 0 : NEO_VARIANT_MEMBER_ARGUMENTS.get(named);
44151
- const field = named ? fields.find(
44152
- (candidate) => candidate.name === (variantMember ?? named)
44153
- ) : fields[index];
44225
+ const field = named ? fieldsByName.get(variantMember ?? named) : fields[index];
44154
44226
  const expectedType = parameter4?.type ?? field?.type;
44155
44227
  const expectedSemanticType = declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME && named === "collectionValue" ? { name: "string" } : expectedType ? substituteSemanticType(semanticType(expectedType), siteBindings) : void 0;
44156
44228
  validateExpression(
@@ -44450,6 +44522,41 @@ function commonSemanticAssignableType(left, right) {
44450
44522
  if (semanticTypesAssignable(right, left)) return left;
44451
44523
  return void 0;
44452
44524
  }
44525
+ function constructionTypesAssignable(actual, expected, scope, environment) {
44526
+ if (actual.name === expected.name && !actual.arguments?.length && !expected.arguments?.length)
44527
+ return true;
44528
+ const owner = scope.get("this")?.name ?? "";
44529
+ const key = `${owner}:${describeType(actual)}>${describeType(expected)}`;
44530
+ const cached = environment.assignability.get(key);
44531
+ if (cached !== void 0) return cached;
44532
+ const declaration = environment.projectIndex.typeByName.get(owner);
44533
+ const context = {
44534
+ project: environment.project,
44535
+ kind: "initializerExpression",
44536
+ ...declaration ? { declaringType: { kind: "named", typeId: declaration.id } } : {}
44537
+ };
44538
+ const pos = { line: 1, column: 1 };
44539
+ let assignable = true;
44540
+ try {
44541
+ assignable = isNeoScriptTypeAssignable(
44542
+ resolveNeoScriptType(
44543
+ semanticAst(actual, pos),
44544
+ context,
44545
+ environment.projectIndex
44546
+ ),
44547
+ resolveNeoScriptType(
44548
+ semanticAst(expected, pos),
44549
+ context,
44550
+ environment.projectIndex
44551
+ ),
44552
+ environment.projectIndex
44553
+ );
44554
+ } catch (error) {
44555
+ if (!(error instanceof CompileError)) throw error;
44556
+ }
44557
+ environment.assignability.set(key, assignable);
44558
+ return assignable;
44559
+ }
44453
44560
  function semanticNumericRank(name) {
44454
44561
  if (name === "int") return 0;
44455
44562
  if (name === "float") return 1;
@@ -44599,7 +44706,10 @@ function inferExpressionType(expression, scope, environment) {
44599
44706
  if (expression.kind === "litString" || expression.kind === "litInterp" || expression.kind === "litTripleString")
44600
44707
  return primitiveType("string");
44601
44708
  if (expression.kind === "new")
44602
- return expression.className ? { name: expression.className } : void 0;
44709
+ return expression.className ? {
44710
+ name: expression.className,
44711
+ ...expression.typeArguments ? { arguments: expression.typeArguments.map(semanticAstType) } : {}
44712
+ } : void 0;
44603
44713
  if (expression.kind === "force") {
44604
44714
  const operand = inferExpressionType(expression.operand, scope, environment);
44605
44715
  return operand ? { ...operand, nullable: false } : void 0;
@@ -44874,14 +44984,19 @@ function classExtends(actual, expected, environment, visited) {
44874
44984
  );
44875
44985
  }
44876
44986
  function matchingDeclaredConstructor(declaration, argumentNames) {
44877
- const names = (argumentNames ?? []).filter(
44878
- (name) => name !== null
44879
- );
44880
- const key = [...names].map((name) => name.toLowerCase()).sort().join(",");
44881
- const matches = declaration.constructors.filter(
44882
- (constructor2) => constructor2.parameters.map((parameter4) => parameter4.name.toLowerCase()).sort().join(",") === key
44987
+ if (declaration.constructors.length === 1)
44988
+ return declaration.constructors[0] ?? null;
44989
+ if (argumentNames.some((name) => name === null)) return null;
44990
+ const names = argumentNames.filter((name) => name !== null);
44991
+ const resolution = resolveNeoScriptNamedOverload(
44992
+ declaration.constructors,
44993
+ names,
44994
+ (constructor2) => constructor2.parameters.map((parameter4) => ({
44995
+ name: parameter4.name,
44996
+ defaulted: parameter4.defaultValue !== void 0
44997
+ }))
44883
44998
  );
44884
- return matches.length === 1 ? matches[0] ?? null : null;
44999
+ return resolution.kind === "resolved" ? resolution.overload : null;
44885
45000
  }
44886
45001
  function semanticType(type) {
44887
45002
  return {
@@ -45230,6 +45345,7 @@ var init_project_source_semantics = __esm({
45230
45345
  init_strict_compile_error();
45231
45346
  init_executable_parser();
45232
45347
  init_project_source_construction_diagnostics();
45348
+ init_declared_constructors();
45233
45349
  init_project_source_variants();
45234
45350
  init_language_spec();
45235
45351
  init_project_source_settlement();
@@ -50866,7 +50982,7 @@ var init_registry = __esm({
50866
50982
  "../packages/manifest/schema-contract/registry.mjs"() {
50867
50983
  "use strict";
50868
50984
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
50869
- cliVersion: "0.72.1",
50985
+ cliVersion: "0.72.2",
50870
50986
  projectFileUploadBatchSize: 32,
50871
50987
  documentRecords: {
50872
50988
  member: {
@@ -110681,7 +110797,8 @@ function* prepareServerOwnedSchemaCommitPasses(args) {
110681
110797
  continue;
110682
110798
  const key = `${change.recordKind}:${change.recordId}`;
110683
110799
  const base = baseScriptRecords.get(key);
110684
- if (base && canonicallyEqual2(
110800
+ if (base && // Rollout-tolerated bodies still need recompilation before reuse.
110801
+ firstRejectedNeoScriptCompiledBody(base, null) === null && !hasPendingNeoScriptDerivation(change.recordKind, base) && canonicallyEqual2(
110685
110802
  authoredScriptRecord(change.recordKind, base),
110686
110803
  authoredScriptRecord(change.recordKind, change.nextData)
110687
110804
  ))
@@ -110733,12 +110850,14 @@ function* prepareServerOwnedSchemaCommitPasses(args) {
110733
110850
  if (kind === "member")
110734
110851
  remappedMembers.set(record7.id, {
110735
110852
  ...moved,
110736
- ...stripClientDerivedBodies("member", record7)
110853
+ createdAt: record7.createdAt,
110854
+ updatedAt: record7.updatedAt
110737
110855
  });
110738
110856
  else
110739
110857
  remappedConstructors.set(record7.id, {
110740
110858
  ...moved,
110741
- ...stripConstructorDerivedBodies(record7)
110859
+ createdAt: record7.createdAt,
110860
+ updatedAt: record7.updatedAt
110742
110861
  });
110743
110862
  }
110744
110863
  }
@@ -114517,6 +114636,7 @@ var init_project_version_schema_commit = __esm({
114517
114636
  "../src/database/project-version-schema-commit.ts"() {
114518
114637
  "use strict";
114519
114638
  init_dialogue_logic_compile_pure();
114639
+ init_neoscript_compiler_revision();
114520
114640
  init_neoscript_source_catalog();
114521
114641
  init_prepared_collection_bindings();
114522
114642
  init_stored_closure_captures();
@@ -148410,6 +148530,14 @@ function projectCompletions(analysis, document, position) {
148410
148530
  }))
148411
148531
  };
148412
148532
  }
148533
+ const contextualEnum = projectContextualEnumCompletions(
148534
+ analysis,
148535
+ document,
148536
+ position
148537
+ );
148538
+ if (contextualEnum) {
148539
+ return { isIncomplete: false, items: contextualEnum };
148540
+ }
148413
148541
  const constructorFields = projectConstructorArgumentCompletions(
148414
148542
  analysis,
148415
148543
  document,
@@ -148436,14 +148564,6 @@ function projectCompletions(analysis, document, position) {
148436
148564
  if (initializerMembers) {
148437
148565
  return { isIncomplete: false, items: initializerMembers };
148438
148566
  }
148439
- const contextualEnum = projectContextualEnumCompletions(
148440
- analysis,
148441
- document,
148442
- position
148443
- );
148444
- if (contextualEnum) {
148445
- return { isIncomplete: false, items: contextualEnum };
148446
- }
148447
148567
  return projectFallbackCompletions(analysis, document, position);
148448
148568
  }
148449
148569
  function projectListenerContext(analysis, document, position) {
@@ -149775,11 +149895,19 @@ function projectCallArguments(tokens, openIndex, delimiterEnds) {
149775
149895
  result.push({
149776
149896
  start: token.range.start,
149777
149897
  named: token.kind === "identifier" && tokens[index + 1]?.text === ":",
149898
+ name: token.kind === "identifier" && tokens[index + 1]?.text === ":" ? token.text : null,
149778
149899
  ...token.kind === "identifier" && (tokens[index + 1]?.text === "," || tokens[index + 1]?.text === ")") ? { identifier: token.text } : {}
149779
149900
  });
149780
149901
  atArgumentStart = false;
149781
149902
  }
149782
- if (token.text === "(" || token.text === "[" || token.text === "{") {
149903
+ if (token.text === "new" && tokens[index + 2]?.text === "<") {
149904
+ let depth = 0;
149905
+ index += 2;
149906
+ for (; index < tokens.length; index++) {
149907
+ if (tokens[index].text === "<") depth++;
149908
+ else if (tokens[index].text === ">" && --depth === 0) break;
149909
+ }
149910
+ } else if (token.text === "(" || token.text === "[" || token.text === "{") {
149783
149911
  index = delimiterEnds.get(index) ?? tokens.length;
149784
149912
  }
149785
149913
  }
@@ -150387,6 +150515,7 @@ function indexConstructionSites(analysis, document, root) {
150387
150515
  while (end < allTokens.length && positionCompare(allTokens[end].range.start, root.range.end) <= 0)
150388
150516
  end++;
150389
150517
  const tokens = allTokens.slice(start, end).filter((token) => token.kind !== "comment" && token.kind !== "eof");
150518
+ let delimiterEnds;
150390
150519
  const language = listenerSourceContext(analysis, document, root.range.start);
150391
150520
  const resolveSourceType = (type) => {
150392
150521
  if (!type) return null;
@@ -150427,15 +150556,17 @@ function indexConstructionSites(analysis, document, root) {
150427
150556
  const declaration = language.project.typeById.get(frame.typeRef.typeId);
150428
150557
  if (!declaration) return null;
150429
150558
  const constructors = declaration.declaredConstructors;
150430
- const parameters = constructors?.length === 1 ? constructors[0]?.parameters : declaration.constructorSignature?.parameters;
150431
- const parameter4 = frame.argumentName ? parameters?.find(
150432
- (candidate) => candidate.name === frame.argumentName
150433
- ) : parameters?.[frame.argumentIndex];
150434
- frame.expectedTypeRef = parameter4 ? substituteTypeParameters(
150435
- "type" in parameter4 ? parameter4.type : parameter4.valueType,
150436
- frame.typeRef,
150437
- declaration
150438
- ) : fieldType(frame.typeRef, frame.argumentName);
150559
+ const parameters = constructors?.length ? constructors[frame.constructorIndex]?.parameters : declaration.constructorSignature?.parameters;
150560
+ const parameter4 = frame.argumentName ? parameters && neoScriptParameterByName(parameters, frame.argumentName) : parameters?.[frame.argumentIndex];
150561
+ if (parameter4) {
150562
+ frame.expectedTypeRef = substituteTypeParameters(
150563
+ "type" in parameter4 ? parameter4.type : parameter4.valueType,
150564
+ frame.typeRef,
150565
+ declaration
150566
+ );
150567
+ } else if (!constructors?.length) {
150568
+ frame.expectedTypeRef = fieldType(frame.typeRef, frame.argumentName);
150569
+ }
150439
150570
  return frame.expectedTypeRef;
150440
150571
  }
150441
150572
  }
@@ -150458,14 +150589,16 @@ function indexConstructionSites(analysis, document, root) {
150458
150589
  const objects = [];
150459
150590
  const snapshot = () => {
150460
150591
  const expected = expectedType();
150592
+ const resolvedExpected = expectedTypeRef();
150593
+ const expectedTypeName = resolvedExpected?.kind === "named" ? language.project.typeById.get(resolvedExpected.typeId)?.name ?? expected?.name : expected?.name;
150461
150594
  const innermost = stack.at(-1);
150462
150595
  const directly = innermost?.kind === "object" ? innermost : null;
150463
150596
  const keys = directly?.keys ?? [];
150464
150597
  const keyCount = keys.length;
150465
150598
  return {
150466
150599
  expectedType: expected,
150467
- expectedTypeRef: expectedTypeRef(),
150468
- expectedTypeName: expected?.name ?? null,
150600
+ expectedTypeRef: resolvedExpected,
150601
+ expectedTypeName: expectedTypeName ?? null,
150469
150602
  enclosingTypeName: objects.at(-1)?.typeName ?? null,
150470
150603
  initializerTypeName: directly?.typeName ?? null,
150471
150604
  get initializerKeys() {
@@ -150581,8 +150714,20 @@ function indexConstructionSites(analysis, document, root) {
150581
150714
  }
150582
150715
  if (token.text === "(") {
150583
150716
  if (sawNew) {
150717
+ const declaration = pendingNewTypeName ? findTypeDeclaration(analysis, pendingNewTypeName) : null;
150718
+ let constructorIndex = 0;
150719
+ if (declaration?.kind === "class" && declaration.constructors.length > 1) {
150720
+ const names = projectCallArguments(
150721
+ tokens,
150722
+ index,
150723
+ delimiterEnds ??= projectDelimiterEnds(tokens)
150724
+ ).map((argument2) => argument2.name);
150725
+ const constructor2 = matchingDeclaredConstructor(declaration, names);
150726
+ constructorIndex = constructor2 ? declaration.constructors.indexOf(constructor2) : -1;
150727
+ }
150584
150728
  stack.push({
150585
150729
  kind: "call",
150730
+ constructorIndex,
150586
150731
  typeName: pendingNewTypeName,
150587
150732
  typeRef: pendingNewTypeRef,
150588
150733
  argumentIndex: 0,
@@ -150669,6 +150814,14 @@ function constructorArgumentType(analysis, frame) {
150669
150814
  if (!declaration || declaration.kind === "global" || declaration.kind === "enum") {
150670
150815
  return null;
150671
150816
  }
150817
+ if (declaration.kind === "class") {
150818
+ const parameters = declaration.headerParameters ?? declaration.constructors[frame.constructorIndex]?.parameters;
150819
+ if (parameters) {
150820
+ const parameter4 = frame.argumentName === null ? parameters[frame.argumentIndex] : neoScriptParameterByName(parameters, frame.argumentName);
150821
+ return parameter4?.type ?? null;
150822
+ }
150823
+ if (declaration.constructors.length > 0) return null;
150824
+ }
150672
150825
  const fields = declarationFields(declaration);
150673
150826
  if (frame.argumentName !== null) {
150674
150827
  return fields.assignableByName.get(frame.argumentName)?.type ?? null;
@@ -150925,6 +151078,7 @@ var init_project_source_language_features = __esm({
150925
151078
  init_project_listener_annotations();
150926
151079
  init_strict_resolver();
150927
151080
  init_type_substitution();
151081
+ init_declared_constructors();
150928
151082
  init_executable_parser();
150929
151083
  init_strict_compile_error();
150930
151084
  init_analyzer();
@@ -190289,7 +190443,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
190289
190443
  async function main() {
190290
190444
  const args = parseArgs(process.argv.slice(2));
190291
190445
  if (args.command === "--version") {
190292
- console.log("0.72.1");
190446
+ console.log("0.72.2");
190293
190447
  return;
190294
190448
  }
190295
190449
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -38520,6 +38520,7 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
38520
38520
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === callee.name
38521
38521
  );
38522
38522
  if (member) {
38523
+ this.validateFunctionReceiver(receiver, callee.name, optional2, pos);
38523
38524
  if (type !== void 0) this.assertMemberAccessible(type, member, pos);
38524
38525
  if (member.static === true) {
38525
38526
  throw new CompileError(
@@ -39668,12 +39669,10 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
39668
39669
  callableSignatureForReceiver(symbol, receiver) {
39669
39670
  let parameters = symbol.parameters ?? [];
39670
39671
  let returnType = symbol.returnType ?? symbol.type;
39671
- if (this.context.projectSourceKind !== "spec" && !(receiver?.kind === "instance" && receiver.dispatch === "base")) {
39672
- return { parameters, returnType };
39673
- }
39674
39672
  if (receiver?.kind !== "instance") return { parameters, returnType };
39675
39673
  const receiverType = receiver.expression.type;
39676
39674
  if (receiverType.kind !== "named") return { parameters, returnType };
39675
+ if (!receiverType.typeArguments?.length) return { parameters, returnType };
39677
39676
  const declaring = this.project.typeById.get(receiverType.typeId);
39678
39677
  if (declaring === void 0) return { parameters, returnType };
39679
39678
  parameters = parameters.map((parameter3) => ({
@@ -40577,6 +40576,21 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40577
40576
  callSiteId: this.nextCallSite(expression.pos)
40578
40577
  };
40579
40578
  }
40579
+ validateFunctionReceiver(receiver, name, optional2, pos) {
40580
+ if (optional2 && !isNullable(receiver.type)) {
40581
+ throw new CompileError(
40582
+ `\`?.\` requires an optional receiver; got ${this.describe(receiver.type)}`,
40583
+ pos
40584
+ );
40585
+ }
40586
+ const receiverType = receiver.optionalChainType ?? receiver.type;
40587
+ if (!optional2 && isNullable(receiverType)) {
40588
+ throw new CompileError(
40589
+ `Function '${name}' is called on ${this.describe(receiverType)}, which may be null. Use optional chaining, force-unwrapping, or a null check before calling it.`,
40590
+ pos
40591
+ );
40592
+ }
40593
+ }
40580
40594
  resolveFunctionStatement(expression, scope) {
40581
40595
  if (expression.kind !== "call") return null;
40582
40596
  const receiverValue = this.callReceiver(expression.callee, scope);
@@ -40740,12 +40754,19 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40740
40754
  }
40741
40755
  }
40742
40756
  const receiver = receiverValue();
40743
- if (receiver.type.kind !== "named") return null;
40744
- const type = this.project.typeById.get(receiver.type.typeId);
40757
+ const memberReceiverType = neoScriptMemberLookupType(receiver.type);
40758
+ if (memberReceiverType.kind !== "named") return null;
40759
+ const type = this.project.typeById.get(memberReceiverType.typeId);
40745
40760
  const symbol = type?.members.find(
40746
40761
  (candidate) => (candidate.kind === "function" || candidate.kind === "method") && candidate.name === memberName
40747
40762
  );
40748
40763
  if (!symbol) return null;
40764
+ this.validateFunctionReceiver(
40765
+ receiver,
40766
+ memberName,
40767
+ expression.callee.optional === true,
40768
+ expression.pos
40769
+ );
40749
40770
  if (type !== void 0) {
40750
40771
  this.assertMemberAccessible(type, symbol, expression.pos);
40751
40772
  }
@@ -40758,7 +40779,10 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40758
40779
  this.validateCallableInContext(symbol, expression.pos);
40759
40780
  const resolved = this.resolveCallableArguments(
40760
40781
  symbol,
40761
- { kind: "instance", expression: receiver },
40782
+ {
40783
+ kind: "instance",
40784
+ expression: memberReceiverType === receiver.type ? receiver : { ...receiver, type: memberReceiverType }
40785
+ },
40762
40786
  expression.args,
40763
40787
  scope,
40764
40788
  expression.pos
@@ -54634,7 +54658,7 @@ var SPECIALIZED_RELATION_KINDS = /* @__PURE__ */ new Set([
54634
54658
 
54635
54659
  // ../packages/manifest/schema-contract/registry.mjs
54636
54660
  var PROJECT_SCHEMA_CONTRACT = Object.freeze({
54637
- cliVersion: "0.72.1",
54661
+ cliVersion: "0.72.2",
54638
54662
  projectFileUploadBatchSize: 32,
54639
54663
  documentRecords: {
54640
54664
  member: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.72.1",
3
+ "version": "0.72.2",
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.72.1 -->
12
+ <!-- reviewed-through-cli: 0.72.2 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -118,7 +118,7 @@ wrappers.
118
118
  The marker near the top of `SKILL.md` must exactly match the package version:
119
119
 
120
120
  ```html
121
- <!-- reviewed-through-cli: 0.72.1 -->
121
+ <!-- reviewed-through-cli: 0.72.2 -->
122
122
  ```
123
123
 
124
124
  The quoted version above is checked too, so this instruction cannot go stale
@@ -176,7 +176,13 @@ indexes only on the original List member. Overrides inherit its indexes, and
176
176
  List getter.
177
177
 
178
178
  Use `.EnumCase` only when context fixes one enum type; otherwise write
179
- `EnumType.Case`. Defaults and settings must be statically analyzable.
179
+ `EnumType.Case`. Constructor parameters supply this context for positional
180
+ and named arguments, including substituted generic parameter types. Defaults
181
+ and settings must be statically analyzable.
182
+
183
+ An explicit construction must be assignable to its receiving type. For example,
184
+ `List<Base>` accepts `new Derived()` when `Derived` inherits `Base`, but rejects
185
+ an unrelated class at `new`. Generic arguments must also be compatible.
180
186
 
181
187
  ## Member and relation rules
182
188
 
@@ -99,6 +99,10 @@ this.Items.Insert(0, item);
99
99
  this.Items.Remove(item);
100
100
  ```
101
101
 
102
+ A generic receiver may call methods from its class or interface constraint,
103
+ including void calls used as statements. Generic arguments substitute through
104
+ the constraint; nullable receivers still require narrowing or null handling.
105
+
102
106
  Prefer `--json` for automation.
103
107
 
104
108
  `neo script check --all` reconstructs the prospective project entirely from
@@ -84,6 +84,9 @@ This names the initializer row. It does not create a stored global. The inline
84
84
  `Quest HardAsStone = @id("...") new Quest();` spelling is also accepted, and
85
85
  formatting moves its ID above the declaration. Do not put an ID in both places.
86
86
 
87
+ A local initializer such as `Delivery DeliveryRow = new()` retains `Delivery`
88
+ when consumed by a base-class member. Aliases retain that same concrete type.
89
+
87
90
  Nested class/list rows remain independently addressable and keep inline IDs:
88
91
 
89
92
  ```neo