@neocompose/cli 0.32.4 → 0.33.0

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,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.33.0] - 2026-08-19
4
+
5
+ ### Added
6
+
7
+ - Add lazy, right-associative conditional expressions with the familiar
8
+ `condition ? whenTrue : whenFalse` spelling. Both result arms are
9
+ target-typed and only the selected arm runs in CLI tests, web previews, and
10
+ the Unity runtime. Project exports now use schema version 24 and compiled
11
+ NeoScript bodies use compiler revision 12 so older runtimes fail at their
12
+ normal compatibility gate.
13
+ - Add `NeoAction.Clear()` as the statement-form way to replace an action's
14
+ stored listener set with the empty set.
15
+ - Allow returned inline `NeoDelegate` lambdas to capture surrounding locals
16
+ and parameters by value. Captures are explicit in revision-12 IR and remain
17
+ available after the delegate is stored or serialized.
18
+ - Accept every NeoScript type grammar in explicit lambda parameters, including
19
+ `decimal`, generic parameters, collections, arrays, and nullable types.
20
+
21
+ ### Fixed
22
+
23
+ - Resolve local type annotations such as `T modifiedValue` against the
24
+ declaring Class's generic parameters instead of synthesizing an unrelated
25
+ type named `T`.
26
+ - Support `Equals(...)` on a non-null generic type parameter. Runtime Class
27
+ values dispatch a compatible one-argument, bool-returning `Equals` Function
28
+ or NSFunction (including overrides); other values use ordinary NeoScript
29
+ equality.
30
+ - Parse `value is Type ? whenTrue : whenFalse` as a conditional expression
31
+ instead of consuming its question mark as a forbidden nullable `is Type?`
32
+ suffix.
33
+ - Use the strict compiler's numeric/nullability join rules when validating
34
+ project-source conditional branches, and make nested-lambda shadowing
35
+ diagnostics independent of whether an outer value was captured earlier.
36
+
3
37
  ## [0.32.4] - 2026-08-18
4
38
 
5
39
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -6467,12 +6467,26 @@ function makeBinary(op, left, right, pos) {
6467
6467
  return { kind: "binary", op, left, right, pos };
6468
6468
  }
6469
6469
  function asASTBinaryOp(text) {
6470
- if (!AST_BINARY_OPS.has(text)) {
6471
- throw new Error(
6472
- `Internal compiler error: '${text}' is not a valid ASTBinaryOp`
6473
- );
6470
+ switch (text) {
6471
+ case "+":
6472
+ case "-":
6473
+ case "*":
6474
+ case "/":
6475
+ case "%":
6476
+ case "==":
6477
+ case "!=":
6478
+ case "<":
6479
+ case ">":
6480
+ case "<=":
6481
+ case ">=":
6482
+ case "&&":
6483
+ case "||":
6484
+ return text;
6485
+ default:
6486
+ throw new Error(
6487
+ `Internal compiler error: '${text}' is not a valid ASTBinaryOp`
6488
+ );
6474
6489
  }
6475
- return text;
6476
6490
  }
6477
6491
  function isAssignmentOp(token) {
6478
6492
  return token.kind === "op" && (token.text === "=" || token.text === "+=" || token.text === "-=" || token.text === "*=" || token.text === "/=" || token.text === "%=");
@@ -6488,7 +6502,7 @@ function describeToken(t) {
6488
6502
  if (t.kind === "eof") return "end of input";
6489
6503
  return `'${t.text}'`;
6490
6504
  }
6491
- var Parser, AST_BINARY_OPS;
6505
+ var Parser;
6492
6506
  var init_strict_parser = __esm({
6493
6507
  "../packages/neoscript-language/src/strict-parser.ts"() {
6494
6508
  "use strict";
@@ -6979,7 +6993,7 @@ var init_strict_parser = __esm({
6979
6993
  // Types
6980
6994
  // ------------------------------------------------------------------
6981
6995
  /** Parses a type annotation (used in varDecls and Dictionary generics). */
6982
- parseType() {
6996
+ parseType(options = {}) {
6983
6997
  const startPos = this.peek().pos;
6984
6998
  let inner;
6985
6999
  const t = this.peek();
@@ -7045,17 +7059,73 @@ var init_strict_parser = __esm({
7045
7059
  pos: arrPos
7046
7060
  };
7047
7061
  }
7048
- if (this.peek().kind === "op" && this.peek().text === "?") {
7062
+ if (this.peek().kind === "op" && this.peek().text === "?" && !(options.conditionalQuestion === true && this.hasMatchingConditionalColon(this.i))) {
7049
7063
  this.next();
7050
7064
  inner = { ...inner, required: false };
7051
7065
  }
7052
7066
  return inner;
7053
7067
  }
7068
+ /**
7069
+ * `is T ? a : b` is ambiguous at the token level because `?` is also the
7070
+ * nullable type suffix. A colon at the same delimiter depth makes it the
7071
+ * conditional operator; nested ternaries are paired before the outer colon.
7072
+ */
7073
+ hasMatchingConditionalColon(questionIndex) {
7074
+ let delimiterDepth2 = 0;
7075
+ let nestedQuestions = 0;
7076
+ for (let index = questionIndex + 1; index < this.tokens.length; index += 1) {
7077
+ const token = this.tokens[index];
7078
+ if (token === void 0) return false;
7079
+ if (token.kind === "punct") {
7080
+ if (token.text === "(" || token.text === "[" || token.text === "{") {
7081
+ delimiterDepth2 += 1;
7082
+ continue;
7083
+ }
7084
+ if (token.text === ")" || token.text === "]" || token.text === "}") {
7085
+ if (delimiterDepth2 === 0) return false;
7086
+ delimiterDepth2 -= 1;
7087
+ continue;
7088
+ }
7089
+ if (delimiterDepth2 !== 0) continue;
7090
+ if (token.text === ";" || token.text === ",") return false;
7091
+ if (token.text !== ":") continue;
7092
+ if (nestedQuestions === 0) return true;
7093
+ nestedQuestions -= 1;
7094
+ continue;
7095
+ }
7096
+ if (delimiterDepth2 === 0 && token.kind === "op" && token.text === "?") {
7097
+ nestedQuestions += 1;
7098
+ }
7099
+ }
7100
+ return false;
7101
+ }
7054
7102
  // ------------------------------------------------------------------
7055
7103
  // Expressions — climbing precedence (lowest first)
7056
7104
  // ------------------------------------------------------------------
7057
7105
  parseExpr() {
7058
- return this.parseCoalesce();
7106
+ return this.parseConditional();
7107
+ }
7108
+ /**
7109
+ * Conditional `condition ? whenTrue : whenFalse`. This is the lowest
7110
+ * precedence expression operator and is right-associative, matching the
7111
+ * familiar C#/TypeScript reading of nested ternaries.
7112
+ */
7113
+ parseConditional() {
7114
+ const condition = this.parseCoalesce();
7115
+ if (!(this.peek().kind === "op" && this.peek().text === "?")) {
7116
+ return condition;
7117
+ }
7118
+ const question = this.next();
7119
+ const whenTrue = this.parseExpr();
7120
+ this.expect("punct", ":");
7121
+ const whenFalse = this.parseConditional();
7122
+ return {
7123
+ kind: "conditional",
7124
+ condition,
7125
+ whenTrue,
7126
+ whenFalse,
7127
+ pos: question.pos
7128
+ };
7059
7129
  }
7060
7130
  /**
7061
7131
  * Null-coalesce `lhs ?? rhs`. Lower precedence than `||` / `&&` so
@@ -7103,7 +7173,7 @@ var init_strict_parser = __esm({
7103
7173
  }
7104
7174
  if (t.kind === "keyword" && t.text === "is") {
7105
7175
  this.next();
7106
- const type = this.parseType();
7176
+ const type = this.parseType({ conditionalQuestion: true });
7107
7177
  const bindingName = this.peek().kind === "ident" ? this.next().text : null;
7108
7178
  left = { kind: "is", operand: left, type, bindingName, pos: t.pos };
7109
7179
  continue;
@@ -7538,13 +7608,8 @@ var init_strict_parser = __esm({
7538
7608
  */
7539
7609
  parseLambdaParam() {
7540
7610
  const startPos = this.peek().pos;
7541
- const t = this.peek();
7542
- if (t.kind === "keyword" && (t.text === "int" || t.text === "float" || t.text === "bool" || t.text === "string" || t.text === "Dictionary")) {
7543
- const type = this.parseType();
7544
- const name2 = this.expect("ident");
7545
- return { type, name: name2.text, pos: startPos };
7546
- }
7547
- if (t.kind === "ident" && this.tokens[this.i + 1]?.kind === "ident") {
7611
+ const afterType = this.scanType(this.i);
7612
+ if (afterType !== null && this.tokens[afterType]?.kind === "ident") {
7548
7613
  const type = this.parseType();
7549
7614
  const name2 = this.expect("ident");
7550
7615
  return { type, name: name2.text, pos: startPos };
@@ -7607,21 +7672,6 @@ var init_strict_parser = __esm({
7607
7672
  return { kind: "litInterp", parts, pos };
7608
7673
  }
7609
7674
  };
7610
- AST_BINARY_OPS = /* @__PURE__ */ new Set([
7611
- "+",
7612
- "-",
7613
- "*",
7614
- "/",
7615
- "%",
7616
- "==",
7617
- "!=",
7618
- "<",
7619
- ">",
7620
- "<=",
7621
- ">=",
7622
- "&&",
7623
- "||"
7624
- ]);
7625
7675
  }
7626
7676
  });
7627
7677
 
@@ -8225,7 +8275,7 @@ var NEOSCRIPT_COMPILER_REVISION;
8225
8275
  var init_strict_ir = __esm({
8226
8276
  "../packages/neoscript-language/src/strict-ir.ts"() {
8227
8277
  "use strict";
8228
- NEOSCRIPT_COMPILER_REVISION = 11;
8278
+ NEOSCRIPT_COMPILER_REVISION = 12;
8229
8279
  }
8230
8280
  });
8231
8281
 
@@ -8628,6 +8678,11 @@ function collectAssignedPathRootsInExpression(expression, roots) {
8628
8678
  collectAssignedPathRootsInExpression(expression.left, roots);
8629
8679
  collectAssignedPathRootsInExpression(expression.right, roots);
8630
8680
  return;
8681
+ case "conditional":
8682
+ collectAssignedPathRootsInExpression(expression.condition, roots);
8683
+ collectAssignedPathRootsInExpression(expression.whenTrue, roots);
8684
+ collectAssignedPathRootsInExpression(expression.whenFalse, roots);
8685
+ return;
8631
8686
  case "unary":
8632
8687
  case "force":
8633
8688
  collectAssignedPathRootsInExpression(expression.operand, roots);
@@ -9515,6 +9570,7 @@ var init_strict_resolver = __esm({
9515
9570
  this.context = context;
9516
9571
  this.source = source;
9517
9572
  this.project = projectIndex ?? createProjectIndex(context.project);
9573
+ this.sourceTokens = source === void 0 ? void 0 : lex2(source);
9518
9574
  const unknown = { kind: "primitive", name: "unknown" };
9519
9575
  this.thisVariable = variable(
9520
9576
  "__this__",
@@ -9560,6 +9616,9 @@ var init_strict_resolver = __esm({
9560
9616
  argumentVariables;
9561
9617
  callSiteCounter = 0;
9562
9618
  lambdaDepth = 0;
9619
+ delegateClosureCounter = 0;
9620
+ delegateCaptureContexts = [];
9621
+ sourceTokens;
9563
9622
  unreachableControlEffectDepth = 0;
9564
9623
  catchBindingCounter = 0;
9565
9624
  catchContexts = [];
@@ -10276,6 +10335,12 @@ var init_strict_resolver = __esm({
10276
10335
  case "assign":
10277
10336
  return this.resolveAssignment(statement, scope);
10278
10337
  case "exprStmt": {
10338
+ const actionClear = this.resolveActionClear(
10339
+ statement.expr,
10340
+ scope,
10341
+ statement.pos
10342
+ );
10343
+ if (actionClear) return actionClear;
10279
10344
  const collectionCall = this.resolveCollectionMutation(
10280
10345
  statement.expr,
10281
10346
  scope,
@@ -10420,7 +10485,10 @@ var init_strict_resolver = __esm({
10420
10485
  pos
10421
10486
  );
10422
10487
  }
10423
- if (!scope.lookup(name)) return;
10488
+ const usedInLexicalOuter = this.delegateCaptureContexts.some(
10489
+ (context) => context.outerScope.lookup(name) !== null
10490
+ );
10491
+ if (!scope.lookup(name) && !usedInLexicalOuter) return;
10424
10492
  throw new CompileError(
10425
10493
  `Cannot declare local '${name}' because that name is already used in an enclosing or current scope.`,
10426
10494
  pos
@@ -10579,7 +10647,9 @@ var init_strict_resolver = __esm({
10579
10647
  }
10580
10648
  const local = statement.target.kind === "ident" ? scope.lookup(statement.target.name) : null;
10581
10649
  if (local?.readonlyBinding) {
10582
- const label = local.readonlyBinding === "catch" ? "catch parameter" : "foreach iterator";
10650
+ let label = "foreach iterator";
10651
+ if (local.readonlyBinding === "catch") label = "catch parameter";
10652
+ if (local.readonlyBinding === "capture") label = "captured value";
10583
10653
  throw new CompileError(
10584
10654
  `Cannot assign to ${label} '${local.name}' because it is read-only.`,
10585
10655
  statement.pos
@@ -11179,6 +11249,8 @@ var init_strict_resolver = __esm({
11179
11249
  type: resultType
11180
11250
  };
11181
11251
  }
11252
+ case "conditional":
11253
+ return this.resolveConditionalExpression(expression, scope, expected);
11182
11254
  case "is": {
11183
11255
  const operand = this.resolveExpression(expression.operand, scope);
11184
11256
  const check = this.resolveType(expression.type);
@@ -11223,19 +11295,40 @@ var init_strict_resolver = __esm({
11223
11295
  ast.pos
11224
11296
  );
11225
11297
  }
11226
- const scope = new Scope(outerScope);
11298
+ const serial = this.delegateClosureCounter++;
11299
+ const scope = new Scope(null);
11300
+ scope.define(
11301
+ scopeVariable(
11302
+ "this",
11303
+ this.thisVariable,
11304
+ this.context.thisClass,
11305
+ void 0,
11306
+ "runtime" /* Runtime */
11307
+ )
11308
+ );
11309
+ const rootSymbol = this.context.project.roots.find(
11310
+ (candidate) => candidate.name === "root"
11311
+ );
11312
+ scope.define(scopeVariable("root", this.rootVariable, rootSymbol?.type));
11227
11313
  const parameters = ast.params.map((parameter4, index) => {
11314
+ this.assertLocalNameAvailable(parameter4.name, outerScope, parameter4.pos);
11228
11315
  this.assertLocalNameAvailable(parameter4.name, scope, parameter4.pos);
11229
11316
  const expectedType = requiredAt(expected.parameterTypes, index);
11230
11317
  const declared = parameter4.type ? this.resolveType(parameter4.type) : expectedType;
11231
- if (!isNeoScriptTypeAssignable(declared, expectedType, this.project) || !isNeoScriptTypeAssignable(expectedType, declared, this.project)) {
11318
+ if (!isNeoScriptTypeAssignable(declared, expectedType, this.project)) {
11319
+ throw new CompileError(
11320
+ `Delegate lambda parameter ${index + 1} type ${this.describe(declared)} is not assignable to ${this.describe(expectedType)}.`,
11321
+ parameter4.pos
11322
+ );
11323
+ }
11324
+ if (!isNeoScriptTypeAssignable(expectedType, declared, this.project)) {
11232
11325
  throw new CompileError(
11233
- `Delegate lambda parameter ${index + 1} must be ${this.describe(expectedType)}, got ${this.describe(declared)}.`,
11326
+ `Delegate lambda parameter ${index + 1} type ${this.describe(declared)} is broader than the declared delegate parameter ${this.describe(expectedType)}.`,
11234
11327
  parameter4.pos
11235
11328
  );
11236
11329
  }
11237
11330
  const item = variable(
11238
- `__arg_${index}__`,
11331
+ `__lambda_${serial}_arg_${index}__`,
11239
11332
  declared,
11240
11333
  void 0,
11241
11334
  this.project
@@ -11251,6 +11344,14 @@ var init_strict_resolver = __esm({
11251
11344
  );
11252
11345
  return item;
11253
11346
  });
11347
+ const captureContext = {
11348
+ outerScope,
11349
+ closureScope: scope,
11350
+ serial,
11351
+ captures: [],
11352
+ byOuterEntry: /* @__PURE__ */ new Map()
11353
+ };
11354
+ this.delegateCaptureContexts.push(captureContext);
11254
11355
  this.functionControlBoundaries.push({
11255
11356
  controlDepth: this.controlContexts.length,
11256
11357
  loopFlowDepth: this.loopFlowContexts.length,
@@ -11269,11 +11370,34 @@ var init_strict_resolver = __esm({
11269
11370
  }
11270
11371
  const action = {
11271
11372
  compilerRevision: NEOSCRIPT_COMPILER_REVISION,
11272
- parameters: [this.thisVariable, this.rootVariable, ...parameters],
11373
+ parameters: [
11374
+ this.thisVariable,
11375
+ this.rootVariable,
11376
+ ...parameters,
11377
+ ...captureContext.captures.map((capture) => capture.parameter)
11378
+ ],
11273
11379
  instructions,
11274
11380
  typeInfo: returnsVoid ? toWireType({ kind: "primitive", name: "null" }, this.project) : toWireType(expected.returnType, this.project)
11275
11381
  };
11276
11382
  const code = this.delegateLambdaSource(ast);
11383
+ if (captureContext.captures.length > 0) {
11384
+ const typeInfo = toWireType(expected, this.project);
11385
+ if (typeInfo.type !== 25 /* Delegate */) {
11386
+ throw new Error(
11387
+ "NeoScript delegate closure lowered with non-delegate type info."
11388
+ );
11389
+ }
11390
+ return {
11391
+ pointer: {
11392
+ type: "delegateClosure" /* DelegateClosure */,
11393
+ typeInfo,
11394
+ action,
11395
+ captures: captureContext.captures.map((capture) => capture.source),
11396
+ ...code === void 0 ? {} : { code }
11397
+ },
11398
+ type: expected
11399
+ };
11400
+ }
11277
11401
  return literal(
11278
11402
  expected,
11279
11403
  code === void 0 ? { action } : { code, action },
@@ -11283,11 +11407,14 @@ var init_strict_resolver = __esm({
11283
11407
  this.expectedReturnStack.pop();
11284
11408
  this.lambdaDepth--;
11285
11409
  this.functionControlBoundaries.pop();
11410
+ this.delegateCaptureContexts.pop();
11286
11411
  }
11287
11412
  }
11288
11413
  delegateLambdaSource(ast) {
11289
- if (this.source === void 0) return void 0;
11290
- const tokens = lex2(this.source);
11414
+ if (this.source === void 0 || this.sourceTokens === void 0) {
11415
+ return void 0;
11416
+ }
11417
+ const tokens = this.sourceTokens;
11291
11418
  const startIndex = tokens.findIndex(
11292
11419
  (token) => token.pos.line === ast.pos.line && token.pos.column === ast.pos.column
11293
11420
  );
@@ -11606,7 +11733,7 @@ var init_strict_resolver = __esm({
11606
11733
  };
11607
11734
  }
11608
11735
  resolveIdentifier(name, scope, pos) {
11609
- const entry = scope.lookup(name);
11736
+ const entry = scope.lookup(name) ?? this.captureDelegateOuterBinding(name);
11610
11737
  if (entry) {
11611
11738
  const ownership = scope.ownership(entry) ?? entry.writability;
11612
11739
  return {
@@ -11659,6 +11786,52 @@ var init_strict_resolver = __esm({
11659
11786
  }
11660
11787
  throw new CompileError(`Unknown identifier '${name}'`, pos);
11661
11788
  }
11789
+ /**
11790
+ * Materializes one free delegate-lambda binding as an explicit trailing
11791
+ * action parameter. The pointer stored beside it is evaluated once at the
11792
+ * lambda expression site, giving NeoScript deterministic by-value capture
11793
+ * semantics that survive return, assignment, and persistence.
11794
+ */
11795
+ captureDelegateOuterBinding(name) {
11796
+ const context = this.delegateCaptureContexts.at(-1);
11797
+ if (context === void 0) return null;
11798
+ let outerEntry = context.outerScope.lookup(name);
11799
+ if (outerEntry === null && this.delegateCaptureContexts.length > 1) {
11800
+ const current = this.delegateCaptureContexts.pop();
11801
+ try {
11802
+ this.captureDelegateOuterBinding(name);
11803
+ } finally {
11804
+ if (current !== void 0) this.delegateCaptureContexts.push(current);
11805
+ }
11806
+ outerEntry = context.outerScope.lookup(name);
11807
+ }
11808
+ if (outerEntry === null) return null;
11809
+ const existing = context.byOuterEntry.get(outerEntry);
11810
+ if (existing !== void 0) return existing;
11811
+ const parameter4 = variable(
11812
+ `__capture_${context.serial}_${context.captures.length}__`,
11813
+ outerEntry.type,
11814
+ void 0,
11815
+ this.project
11816
+ );
11817
+ const entry = scopeVariable(
11818
+ name,
11819
+ parameter4,
11820
+ outerEntry.type,
11821
+ "local" /* Local */,
11822
+ "local" /* Local */,
11823
+ outerEntry.writeRoot,
11824
+ "capture"
11825
+ );
11826
+ const source = outerEntry.pointer ?? {
11827
+ type: "variable" /* Variable */,
11828
+ variableId: outerEntry.variableId
11829
+ };
11830
+ context.byOuterEntry.set(outerEntry, entry);
11831
+ context.captures.push({ outerEntry, source, parameter: parameter4, entry });
11832
+ context.closureScope.define(entry);
11833
+ return entry;
11834
+ }
11662
11835
  resolveContextualEnum(optionName, expected, pos) {
11663
11836
  if (!expected || expected.kind !== "named") {
11664
11837
  throw new CompileError(
@@ -12838,6 +13011,37 @@ var init_strict_resolver = __esm({
12838
13011
  );
12839
13012
  }
12840
13013
  const memberReceiverType = neoScriptMemberLookupType(receiver.type);
13014
+ if (callee.name === "Equals" && memberReceiverType.kind === "typeParameter") {
13015
+ if (optional) {
13016
+ throw new CompileError(
13017
+ "Equals cannot be optional-chained on a generic receiver; narrow it with a null check first.",
13018
+ pos
13019
+ );
13020
+ }
13021
+ if (isNullable(receiver.type)) {
13022
+ throw new CompileError(
13023
+ "Equals cannot be called on an optional generic receiver; narrow it with a null check first.",
13024
+ pos
13025
+ );
13026
+ }
13027
+ requireArgCount("Equals", argumentsList2, 1, pos);
13028
+ const other = this.resolveExpression(
13029
+ requiredAt(argumentsList2, 0),
13030
+ scope,
13031
+ { ...memberReceiverType, nullable: true }
13032
+ );
13033
+ return {
13034
+ pointer: {
13035
+ type: "callFunction" /* CallFunction */,
13036
+ memberKey: "Equals",
13037
+ receiver: { kind: "instance", pointer: receiver.pointer },
13038
+ args: [other.pointer],
13039
+ missingMemberFallback: "valueEquality",
13040
+ callSiteId: this.nextCallSite(pos)
13041
+ },
13042
+ type: { kind: "primitive", name: "bool" }
13043
+ };
13044
+ }
12841
13045
  if (memberReceiverType.kind === "named") {
12842
13046
  const type = this.project.typeById.get(memberReceiverType.typeId);
12843
13047
  const member = type?.members.find(
@@ -14434,6 +14638,159 @@ var init_strict_resolver = __esm({
14434
14638
  args
14435
14639
  };
14436
14640
  }
14641
+ /**
14642
+ * `NeoAction.Clear()` resets the stored listener set. It lowers to an
14643
+ * ordinary whole-member assignment so every existing write host observes
14644
+ * the same overlay and write-intent contract as `+=` / `-=`.
14645
+ */
14646
+ resolveActionClear(expression, scope, pos) {
14647
+ if (expression.kind !== "call" || expression.callee.kind !== "member" || expression.callee.name !== "Clear") {
14648
+ return null;
14649
+ }
14650
+ const receiver = this.resolveExpression(expression.callee.receiver, scope);
14651
+ if (receiver.type.kind !== "action") return null;
14652
+ if (expression.callee.optional) {
14653
+ throw new CompileError(
14654
+ `${this.describe(receiver.type)} is never null, so optional-chained Clear is not supported.`,
14655
+ pos
14656
+ );
14657
+ }
14658
+ requireArgCount("Clear", expression.args, 0, pos);
14659
+ if (!isActionMemberPointer(receiver.pointer)) {
14660
+ throw new CompileError(
14661
+ `Cannot clear ${this.describe(receiver.type)} that is not a member reference; an action is not a value. Clear it through the member reference directly.`,
14662
+ pos
14663
+ );
14664
+ }
14665
+ if (receiver.symbol?.computed === true) {
14666
+ throw new CompileError(
14667
+ `Cannot clear NeoScript property '${receiver.symbol.name}'; an action listener set must be stored data.`,
14668
+ pos
14669
+ );
14670
+ }
14671
+ const writability = receiver.writability;
14672
+ if (writability === "immutable" /* Immutable */) {
14673
+ throw new CompileError(
14674
+ "Cannot clear an action whose effective storage is immutable.",
14675
+ pos
14676
+ );
14677
+ }
14678
+ if (!writability) {
14679
+ throw new CompileError(
14680
+ "Cannot clear an action whose member writability could not be resolved.",
14681
+ pos
14682
+ );
14683
+ }
14684
+ if (writability === "readOnly" /* ReadOnly */) {
14685
+ throw new CompileError("Cannot clear a read-only action.", pos);
14686
+ }
14687
+ this.validateConstructorWriteTarget(
14688
+ receiver.pointer,
14689
+ "mutate",
14690
+ pos,
14691
+ scope,
14692
+ true
14693
+ );
14694
+ const path = canonicalPath(expression.callee.receiver);
14695
+ if (path) {
14696
+ const rootEntry = scope.lookup(pathRoot(path));
14697
+ if (rootEntry) scope.invalidate(rootEntry);
14698
+ }
14699
+ const actionTypeInfo = receiver.wireType ?? toWireType(receiver.type, this.project);
14700
+ return {
14701
+ type: "assign" /* Assign */,
14702
+ target: {
14703
+ pointer: receiver.pointer,
14704
+ typeInfo: actionTypeInfo,
14705
+ writability
14706
+ },
14707
+ operator: "=" /* Assign */,
14708
+ pointer: {
14709
+ type: "value" /* Value */,
14710
+ value: { typeInfo: actionTypeInfo, value: { listeners: [] } }
14711
+ }
14712
+ };
14713
+ }
14714
+ resolveConditionalExpression(expression, scope, expected) {
14715
+ const condition = this.toBool(
14716
+ this.resolveExpression(expression.condition, scope),
14717
+ expression.condition.pos
14718
+ );
14719
+ const trueScope = new Scope(scope);
14720
+ this.applyFactsToScope(
14721
+ scope,
14722
+ trueScope,
14723
+ factsWhenTrue(expression.condition)
14724
+ );
14725
+ const falseScope = new Scope(scope);
14726
+ this.applyFactsToScope(
14727
+ scope,
14728
+ falseScope,
14729
+ factsWhenFalse(expression.condition)
14730
+ );
14731
+ let whenTrue;
14732
+ let whenFalse;
14733
+ if (expected) {
14734
+ whenTrue = this.resolveExpression(
14735
+ expression.whenTrue,
14736
+ trueScope,
14737
+ expected
14738
+ );
14739
+ whenFalse = this.resolveExpression(
14740
+ expression.whenFalse,
14741
+ falseScope,
14742
+ expected
14743
+ );
14744
+ } else if (expression.whenTrue.kind === "litNull" || expression.whenTrue.kind === "contextualEnum") {
14745
+ whenFalse = this.resolveExpression(expression.whenFalse, falseScope);
14746
+ whenTrue = this.resolveExpression(expression.whenTrue, trueScope, {
14747
+ ...whenFalse.type,
14748
+ nullable: true
14749
+ });
14750
+ } else {
14751
+ whenTrue = this.resolveExpression(expression.whenTrue, trueScope);
14752
+ whenFalse = this.resolveExpression(
14753
+ expression.whenFalse,
14754
+ falseScope,
14755
+ expression.whenFalse.kind === "litNull" ? { ...whenTrue.type, nullable: true } : whenTrue.type
14756
+ );
14757
+ }
14758
+ const common = commonNeoScriptAssignableType(
14759
+ whenTrue.type,
14760
+ whenFalse.type,
14761
+ this.project
14762
+ );
14763
+ const resultType = common ?? (expected && isNeoScriptTypeAssignable(whenTrue.type, expected, this.project) && isNeoScriptTypeAssignable(whenFalse.type, expected, this.project) ? expected : null);
14764
+ if (!resultType) {
14765
+ throw new CompileError(
14766
+ `Conditional branches ${this.describe(whenTrue.type)} and ${this.describe(whenFalse.type)} do not have a common assignable type.`,
14767
+ expression.pos
14768
+ );
14769
+ }
14770
+ if (expected) {
14771
+ this.requireAssignable(
14772
+ whenTrue.type,
14773
+ expected,
14774
+ expression.whenTrue.pos,
14775
+ "conditional true branch"
14776
+ );
14777
+ this.requireAssignable(
14778
+ whenFalse.type,
14779
+ expected,
14780
+ expression.whenFalse.pos,
14781
+ "conditional false branch"
14782
+ );
14783
+ }
14784
+ return {
14785
+ pointer: {
14786
+ type: "conditional" /* Conditional */,
14787
+ condition: condition.pointer,
14788
+ whenTrue: whenTrue.pointer,
14789
+ whenFalse: whenFalse.pointer
14790
+ },
14791
+ type: resultType
14792
+ };
14793
+ }
14437
14794
  resolveBinary(expression, scope) {
14438
14795
  let left;
14439
14796
  let preResolvedRight;
@@ -14646,6 +15003,15 @@ var init_strict_resolver = __esm({
14646
15003
  type.pos
14647
15004
  );
14648
15005
  }
15006
+ const declaringTypeRef = this.context.declaringType ?? this.context.thisClass;
15007
+ const declaringType = declaringTypeRef?.kind === "named" ? this.project.typeById.get(declaringTypeRef.typeId) : void 0;
15008
+ const typeParameter2 = declaringType?.typeParameters?.find(
15009
+ (candidate) => candidate.name === type.name
15010
+ );
15011
+ if (typeParameter2?.type.kind === "typeParameter") {
15012
+ resolved = typeParameter2.type;
15013
+ break;
15014
+ }
14649
15015
  const builtin = namedPrimitive(type.name);
14650
15016
  if (builtin) {
14651
15017
  resolved = { kind: "primitive", name: builtin };
@@ -22148,6 +22514,11 @@ function collectIdentifierReads(expression, names) {
22148
22514
  walk(node.left, visible);
22149
22515
  walk(node.right, visible);
22150
22516
  return;
22517
+ case "conditional":
22518
+ walk(node.condition, visible);
22519
+ walk(node.whenTrue, visible);
22520
+ walk(node.whenFalse, visible);
22521
+ return;
22151
22522
  case "unary":
22152
22523
  case "force":
22153
22524
  case "is":
@@ -23185,6 +23556,50 @@ function validateExpression(expression, expected, scope, environment, uri, range
23185
23556
  );
23186
23557
  return;
23187
23558
  }
23559
+ if (expression.kind === "conditional") {
23560
+ validateExpression(
23561
+ expression.condition,
23562
+ primitiveType("bool"),
23563
+ scope,
23564
+ environment,
23565
+ uri,
23566
+ range2,
23567
+ diagnostics,
23568
+ anchor
23569
+ );
23570
+ const trueType = inferExpressionType2(
23571
+ expression.whenTrue,
23572
+ scope,
23573
+ environment
23574
+ );
23575
+ const falseType = inferExpressionType2(
23576
+ expression.whenFalse,
23577
+ scope,
23578
+ environment
23579
+ );
23580
+ const branchType = expected ?? commonSemanticAssignableType(trueType, falseType);
23581
+ validateExpression(
23582
+ expression.whenTrue,
23583
+ branchType,
23584
+ scope,
23585
+ environment,
23586
+ uri,
23587
+ range2,
23588
+ diagnostics,
23589
+ anchor
23590
+ );
23591
+ validateExpression(
23592
+ expression.whenFalse,
23593
+ branchType,
23594
+ scope,
23595
+ environment,
23596
+ uri,
23597
+ range2,
23598
+ diagnostics,
23599
+ anchor
23600
+ );
23601
+ return;
23602
+ }
23188
23603
  if (expression.kind === "new") {
23189
23604
  const typeName = expression.className ?? expected?.name;
23190
23605
  const declaration = typeName ? environment.types.get(typeName) : void 0;
@@ -23493,13 +23908,19 @@ function substituteSemanticType(type, bindings) {
23493
23908
  return { ...type, arguments: argumentsList2 };
23494
23909
  }
23495
23910
  function semanticTypesAssignable(actual, expected) {
23496
- if (actual.name === expected.name) return true;
23911
+ if (actual.name === "null") {
23912
+ return expected.name === "null" || expected.nullable === true;
23913
+ }
23914
+ if (expected.name === "null") return false;
23915
+ if (actual.name === expected.name) {
23916
+ return actual.nullable !== true || expected.nullable === true;
23917
+ }
23497
23918
  if (actual.name === "void") return false;
23498
23919
  if (actual.name === "int" && (expected.name === "float" || expected.name === "decimal")) {
23499
- return true;
23920
+ return actual.nullable !== true || expected.nullable === true;
23500
23921
  }
23501
23922
  if (actual.name === "float" && expected.name === "decimal") {
23502
- return true;
23923
+ return actual.nullable !== true || expected.nullable === true;
23503
23924
  }
23504
23925
  if (Object.prototype.hasOwnProperty.call(
23505
23926
  NEO_PROJECT_SOURCE_RECORD_CONTRACT.enums,
@@ -23507,12 +23928,42 @@ function semanticTypesAssignable(actual, expected) {
23507
23928
  )) {
23508
23929
  return false;
23509
23930
  }
23510
- const primitiveNames = /* @__PURE__ */ new Set(["bool", "int", "float", "decimal", "string"]);
23511
- if (primitiveNames.has(expected.name) && primitiveNames.has(actual.name)) {
23931
+ if (SEMANTIC_PRIMITIVE_TYPE_NAMES.has(expected.name) && SEMANTIC_PRIMITIVE_TYPE_NAMES.has(actual.name)) {
23512
23932
  return false;
23513
23933
  }
23514
23934
  return true;
23515
23935
  }
23936
+ function commonSemanticAssignableType(left, right) {
23937
+ if (left === void 0) return right;
23938
+ if (right === void 0) return left;
23939
+ if (left.name === "null" && right.name === "null") return left;
23940
+ if (left.name === "null") return { ...right, nullable: true };
23941
+ if (right.name === "null") return { ...left, nullable: true };
23942
+ if (left.name === right.name) {
23943
+ return {
23944
+ ...left,
23945
+ nullable: left.nullable === true || right.nullable === true
23946
+ };
23947
+ }
23948
+ const leftRank = semanticNumericRank(left.name);
23949
+ const rightRank = semanticNumericRank(right.name);
23950
+ if (leftRank !== void 0 && rightRank !== void 0) {
23951
+ const name = leftRank >= rightRank ? left.name : right.name;
23952
+ return {
23953
+ name,
23954
+ nullable: left.nullable === true || right.nullable === true
23955
+ };
23956
+ }
23957
+ if (semanticTypesAssignable(left, right)) return right;
23958
+ if (semanticTypesAssignable(right, left)) return left;
23959
+ return void 0;
23960
+ }
23961
+ function semanticNumericRank(name) {
23962
+ if (name === "int") return 0;
23963
+ if (name === "float") return 1;
23964
+ if (name === "decimal") return 2;
23965
+ return void 0;
23966
+ }
23516
23967
  function describeType(type) {
23517
23968
  const argumentsList2 = type.arguments?.length ? `<${type.arguments.map(describeType).join(", ")}>` : "";
23518
23969
  return `${type.name}${argumentsList2}${type.nullable ? "?" : ""}`;
@@ -23556,10 +24007,24 @@ function inferExpressionType2(expression, scope, environment) {
23556
24007
  if (expression.kind === "litBool") return primitiveType("bool");
23557
24008
  if (expression.kind === "litInt") return primitiveType("int");
23558
24009
  if (expression.kind === "litFloat") return primitiveType("float");
24010
+ if (expression.kind === "litNull") return { name: "null", nullable: true };
23559
24011
  if (expression.kind === "litString" || expression.kind === "litInterp" || expression.kind === "litTripleString")
23560
24012
  return primitiveType("string");
23561
24013
  if (expression.kind === "new")
23562
24014
  return expression.className ? { name: expression.className } : void 0;
24015
+ if (expression.kind === "conditional") {
24016
+ const whenTrue = inferExpressionType2(
24017
+ expression.whenTrue,
24018
+ scope,
24019
+ environment
24020
+ );
24021
+ const whenFalse = inferExpressionType2(
24022
+ expression.whenFalse,
24023
+ scope,
24024
+ environment
24025
+ );
24026
+ return commonSemanticAssignableType(whenTrue, whenFalse);
24027
+ }
23563
24028
  if (expression.kind === "member") {
23564
24029
  if (expression.receiver.kind === "ident" && environment.enums.has(expression.receiver.name)) {
23565
24030
  return { name: expression.receiver.name };
@@ -24073,7 +24538,7 @@ function classDerivesFromName(name, ancestorName, environment, seen = /* @__PURE
24073
24538
  return classDerivesFromName(base.name, ancestorName, environment, seen);
24074
24539
  });
24075
24540
  }
24076
- var NEO_VARIANT_FOLDER_SETTING, primitiveType, IDENTIFIER_PATTERN, LIST_COLUMN_INHERITANCE_KEY;
24541
+ var NEO_VARIANT_FOLDER_SETTING, primitiveType, IDENTIFIER_PATTERN, LIST_COLUMN_INHERITANCE_KEY, SEMANTIC_PRIMITIVE_TYPE_NAMES;
24077
24542
  var init_project_source_semantics = __esm({
24078
24543
  "../packages/neoscript-language/src/project-source-semantics.ts"() {
24079
24544
  "use strict";
@@ -24090,6 +24555,13 @@ var init_project_source_semantics = __esm({
24090
24555
  primitiveType = (name) => ({ name });
24091
24556
  IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
24092
24557
  LIST_COLUMN_INHERITANCE_KEY = "__other__";
24558
+ SEMANTIC_PRIMITIVE_TYPE_NAMES = /* @__PURE__ */ new Set([
24559
+ "bool",
24560
+ "int",
24561
+ "float",
24562
+ "decimal",
24563
+ "string"
24564
+ ]);
24093
24565
  }
24094
24566
  });
24095
24567
 
@@ -41658,6 +42130,32 @@ function isNSPointerCoalesce(value) {
41658
42130
  if (!isNSPointer(v.left)) return false;
41659
42131
  return isNSPointer(v.right);
41660
42132
  }
42133
+ function isNSPointerConditional(value) {
42134
+ if (typeof value !== "object" || value === null) return false;
42135
+ if (!("type" in value) || value.type !== "conditional" /* conditional */) {
42136
+ return false;
42137
+ }
42138
+ if (!("condition" in value) || !isNSPointer(value.condition)) return false;
42139
+ if (!("whenTrue" in value) || !isNSPointer(value.whenTrue)) return false;
42140
+ return "whenFalse" in value && isNSPointer(value.whenFalse);
42141
+ }
42142
+ function isNSPointerDelegateClosure(value) {
42143
+ if (typeof value !== "object" || value === null) return false;
42144
+ if (!("type" in value) || value.type !== "delegateClosure" /* delegateClosure */) {
42145
+ return false;
42146
+ }
42147
+ if (!("typeInfo" in value) || !isNSTypeInfo(value.typeInfo) || value.typeInfo.type !== 25 /* NSDelegate */) {
42148
+ return false;
42149
+ }
42150
+ if (!("action" in value) || !isNSFunctionWithReturnType(value.action)) {
42151
+ return false;
42152
+ }
42153
+ if (!("captures" in value) || !Array.isArray(value.captures) || !value.captures.every(isNSPointer)) {
42154
+ return false;
42155
+ }
42156
+ if (!("code" in value) || value.code === void 0) return true;
42157
+ return typeof value.code === "string" && value.code.length > 0;
42158
+ }
41661
42159
  function isNSPointerToBool(value) {
41662
42160
  const v = value;
41663
42161
  if (v?.type !== "toBool" /* toBool */) return false;
@@ -41685,6 +42183,12 @@ function isNSPointerCallFunction(value) {
41685
42183
  if (v.optional !== void 0 && typeof v.optional !== "boolean") {
41686
42184
  return false;
41687
42185
  }
42186
+ if (v.missingMemberFallback !== void 0 && v.missingMemberFallback !== "valueEquality") {
42187
+ return false;
42188
+ }
42189
+ if (v.missingMemberFallback === "valueEquality" && (!hasMemberKey || v.receiver?.kind !== "instance" || v.args.length !== 1)) {
42190
+ return false;
42191
+ }
41688
42192
  return true;
41689
42193
  }
41690
42194
  function isNSPointerCallDelegate(value) {
@@ -41725,7 +42229,7 @@ function isNSPointerFunctionErrorCheck(value) {
41725
42229
  return isNSFunctionErrorCheckMode(v.mode);
41726
42230
  }
41727
42231
  function isNSPointer(value) {
41728
- 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) || isNSPointerToBool(value) || isNSPointerStringify(value) || isNSPointerCallFunction(value) || isNSPointerCallDelegate(value) || isNSPointerCallAction(value) || isNSPointerVariant(value) || isNSPointerFunctionErrorCheck(value);
42232
+ 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) || isNSPointerCallFunction(value) || isNSPointerCallDelegate(value) || isNSPointerCallAction(value) || isNSPointerVariant(value) || isNSPointerFunctionErrorCheck(value);
41729
42233
  }
41730
42234
  function isNSPointers(value) {
41731
42235
  return Array.isArray(value) && value.every(isNSPointer);
@@ -41794,24 +42298,7 @@ function isNSFunctionWithReturnType(value) {
41794
42298
  if (!Array.isArray(v.parameters)) return false;
41795
42299
  if (!v.parameters.every(isNSVariable)) return false;
41796
42300
  if (!isNSInstructions(v.instructions)) return false;
41797
- if ([
41798
- "for" /* for */,
41799
- "forEach" /* forEach */,
41800
- "break" /* break */,
41801
- "continue" /* continue */
41802
- ].some((type) => nsInstructionsContainType(v.instructions ?? [], type)) && (v.compilerRevision ?? 1) < 4) {
41803
- return false;
41804
- }
41805
- if (nsInstructionsContainType(v.instructions, "switch" /* switch */) && (v.compilerRevision ?? 1) < 5) {
41806
- return false;
41807
- }
41808
- if (nsInstructionsContainType(v.instructions, "try" /* try */) && (v.compilerRevision ?? 1) < 6) {
41809
- return false;
41810
- }
41811
- if ((v.compilerRevision ?? 1) < 8 && ([
41812
- "addActionListener" /* addActionListener */,
41813
- "removeActionListener" /* removeActionListener */
41814
- ].some((type) => nsInstructionsContainType(v.instructions ?? [], type)) || nsIRContainsPointerType(v.instructions, "callAction" /* callAction */))) {
42301
+ if ((v.compilerRevision ?? 1) < NEOSCRIPT_COMPILER_REVISION && (v.compilerRevision ?? 1) < minimumNeoScriptCompilerRevisionForIR(v.instructions)) {
41815
42302
  return false;
41816
42303
  }
41817
42304
  if (!isNSTypeInfo(v.typeInfo)) return false;
@@ -42252,44 +42739,32 @@ function isNSInstructionTry(value) {
42252
42739
  function isNSInstruction(value) {
42253
42740
  return isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
42254
42741
  }
42255
- function nsInstructionsContainType(instructions, type) {
42256
- return instructions.some((instruction) => {
42257
- if (instruction.type === type) return true;
42258
- if (instruction.type === "if" /* if */) {
42259
- return instruction.branches.some(
42260
- (branch) => nsInstructionsContainType(branch.instructions, type)
42261
- ) || instruction.else !== null && instruction.else !== void 0 && nsInstructionsContainType(instruction.else, type);
42262
- }
42263
- if (instruction.type === "for" /* for */ || instruction.type === "forEach" /* forEach */) {
42264
- return nsInstructionsContainType(instruction.instructions, type);
42265
- }
42266
- if (instruction.type === "switch" /* switch */) {
42267
- return instruction.sections.some(
42268
- (section) => nsInstructionsContainType(section.instructions, type)
42269
- ) || instruction.defaultInstructions !== null && instruction.defaultInstructions !== void 0 && nsInstructionsContainType(instruction.defaultInstructions, type);
42270
- }
42271
- if (instruction.type === "try" /* try */) {
42272
- return nsInstructionsContainType(instruction.instructions, type) || instruction.catches.some(
42273
- (clause) => nsInstructionsContainType(clause.instructions, type)
42742
+ function minimumNeoScriptCompilerRevisionForIR(node, visited = /* @__PURE__ */ new Set()) {
42743
+ if (Array.isArray(node)) {
42744
+ let minimum2 = 1;
42745
+ for (const entry of node) {
42746
+ minimum2 = Math.max(
42747
+ minimum2,
42748
+ minimumNeoScriptCompilerRevisionForIR(entry, visited)
42274
42749
  );
42275
42750
  }
42276
- return false;
42277
- });
42278
- }
42279
- function nsIRContainsPointerType(node, type, visited = /* @__PURE__ */ new Set()) {
42280
- if (Array.isArray(node)) {
42281
- return node.some((entry) => nsIRContainsPointerType(entry, type, visited));
42751
+ return minimum2;
42282
42752
  }
42283
- if (typeof node !== "object" || node === null) return false;
42284
- if (visited.has(node)) return false;
42753
+ if (typeof node !== "object" || node === null) return 1;
42754
+ if (visited.has(node)) return 1;
42285
42755
  visited.add(node);
42286
- const record3 = node;
42287
- if (record3.type === type) return true;
42288
- const isLiteralValue = "typeInfo" in record3 && "value" in record3 && !("type" in record3);
42289
- return Object.entries(record3).some(([key, child]) => {
42290
- if (isLiteralValue && key === "value") return false;
42291
- return nsIRContainsPointerType(child, type, visited);
42292
- });
42756
+ const discriminatorRevision = "type" in node && typeof node.type === "string" ? MINIMUM_REVISION_BY_IR_DISCRIMINATOR.get(node.type) ?? 1 : 1;
42757
+ const fallbackRevision = "missingMemberFallback" in node && node.missingMemberFallback === "valueEquality" ? 12 : 1;
42758
+ let minimum = Math.max(discriminatorRevision, fallbackRevision);
42759
+ const isLiteralValue = "typeInfo" in node && "value" in node && !("type" in node);
42760
+ for (const [key, child] of Object.entries(node)) {
42761
+ if (isLiteralValue && key === "value") continue;
42762
+ minimum = Math.max(
42763
+ minimum,
42764
+ minimumNeoScriptCompilerRevisionForIR(child, visited)
42765
+ );
42766
+ }
42767
+ return minimum;
42293
42768
  }
42294
42769
  function isNSInstructions(value) {
42295
42770
  return Array.isArray(value) && value.every(isNSInstruction);
@@ -42337,7 +42812,7 @@ function isNSVoidBody(value) {
42337
42812
  if (value.typeInfo.type !== 0 /* Null */) return false;
42338
42813
  return value.typeInfo.required === true;
42339
42814
  }
42340
- var NS_STRING_OPS, NS_DECIMAL_OPS, NS_MATH_OPS;
42815
+ var NS_STRING_OPS, NS_DECIMAL_OPS, NS_MATH_OPS, MINIMUM_REVISION_BY_IR_DISCRIMINATOR;
42341
42816
  var init_neoscript_guards = __esm({
42342
42817
  "../src/models/neoscript/neoscript-guards.ts"() {
42343
42818
  "use strict";
@@ -42370,6 +42845,20 @@ var init_neoscript_guards = __esm({
42370
42845
  "sign",
42371
42846
  "sqrt"
42372
42847
  ];
42848
+ MINIMUM_REVISION_BY_IR_DISCRIMINATOR = /* @__PURE__ */ new Map([
42849
+ ["for" /* for */, 4],
42850
+ ["forEach" /* forEach */, 4],
42851
+ ["break" /* break */, 4],
42852
+ ["continue" /* continue */, 4],
42853
+ ["switch" /* switch */, 5],
42854
+ ["try" /* try */, 6],
42855
+ ["callDelegate" /* callDelegate */, 7],
42856
+ ["addActionListener" /* addActionListener */, 8],
42857
+ ["removeActionListener" /* removeActionListener */, 8],
42858
+ ["callAction" /* callAction */, 8],
42859
+ ["conditional" /* conditional */, 12],
42860
+ ["delegateClosure" /* delegateClosure */, 12]
42861
+ ]);
42373
42862
  }
42374
42863
  });
42375
42864
 
@@ -43340,11 +43829,18 @@ function isNSDelegateClosureValueDraft(value) {
43340
43829
  function isNSDelegateClosureValue(value) {
43341
43830
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
43342
43831
  const record3 = value;
43343
- const keys = record3.code === void 0 ? ["action"] : ["code", "action"];
43832
+ const keys = [
43833
+ ...record3.code === void 0 ? [] : ["code"],
43834
+ "action",
43835
+ ...record3.captures === void 0 ? [] : ["captures"]
43836
+ ];
43344
43837
  if (!hasExactKeys(record3, keys)) return false;
43345
43838
  if (record3.code !== void 0 && (typeof record3.code !== "string" || record3.code.length === 0)) {
43346
43839
  return false;
43347
43840
  }
43841
+ if (record3.captures !== void 0 && !Array.isArray(record3.captures)) {
43842
+ return false;
43843
+ }
43348
43844
  return isNSFunctionWithReturnType(record3.action);
43349
43845
  }
43350
43846
  function isNSDelegateValue(value) {
@@ -56100,6 +56596,17 @@ function expressionReadsRuntimeIdentifier(expression, runtimeIdentifiers) {
56100
56596
  case "binary":
56101
56597
  case "coalesce":
56102
56598
  return expressionReadsRuntimeIdentifier(expression.left, runtimeIdentifiers) || expressionReadsRuntimeIdentifier(expression.right, runtimeIdentifiers);
56599
+ case "conditional":
56600
+ return expressionReadsRuntimeIdentifier(
56601
+ expression.condition,
56602
+ runtimeIdentifiers
56603
+ ) || expressionReadsRuntimeIdentifier(
56604
+ expression.whenTrue,
56605
+ runtimeIdentifiers
56606
+ ) || expressionReadsRuntimeIdentifier(
56607
+ expression.whenFalse,
56608
+ runtimeIdentifiers
56609
+ );
56103
56610
  case "unary":
56104
56611
  case "force":
56105
56612
  case "is":
@@ -56131,6 +56638,7 @@ function isEvaluatedOnlyExpression(expression) {
56131
56638
  return isEvaluatedOnlyExpression(expression.expression);
56132
56639
  case "binary":
56133
56640
  case "coalesce":
56641
+ case "conditional":
56134
56642
  case "index":
56135
56643
  case "is":
56136
56644
  case "force":
@@ -67071,6 +67579,9 @@ function evalPointer(pointer, scope, ctx) {
67071
67579
  (arg) => evalPointer(arg, scope, ctx)
67072
67580
  );
67073
67581
  const member = pointer.receiver.kind === "static" ? evalMemberById(ctx.vm, pointer.receiver.memberId) : resolveEffectiveCallableMember(pointer, innerThis, ctx);
67582
+ if (member !== null && pointer.missingMemberFallback === "valueEquality") {
67583
+ assertValueEqualitySignature(member, innerThis, ctx);
67584
+ }
67074
67585
  const args = fillCallableCallSiteArguments(suppliedArgs, member, ctx);
67075
67586
  const interceptedMemberId = member?.id ?? pointer.memberId ?? pointer.memberKey ?? pointer.callSiteId;
67076
67587
  consumeBudget(ctx, "workUnits", 1, "work unit");
@@ -67084,6 +67595,9 @@ function evalPointer(pointer, scope, ctx) {
67084
67595
  });
67085
67596
  if (intercepted?.handled === true) return intercepted.value;
67086
67597
  if (member === null) {
67598
+ if (pointer.missingMemberFallback === "valueEquality") {
67599
+ return jsEqual(innerThis, suppliedArgs[0]);
67600
+ }
67087
67601
  throw new NSGetterRuntimeError(
67088
67602
  `Function call '${pointer.memberKey ?? pointer.memberId ?? pointer.callSiteId}' could not be resolved on the receiver's runtime class.`
67089
67603
  );
@@ -67197,6 +67711,24 @@ function evalPointer(pointer, scope, ctx) {
67197
67711
  if (left !== null && left !== void 0) return left;
67198
67712
  return evalPointer(pointer.right, scope, ctx);
67199
67713
  }
67714
+ case "conditional" /* conditional */: {
67715
+ const condition = evalPointer(pointer.condition, scope, ctx);
67716
+ return evalPointer(
67717
+ jsTruthy(condition) ? pointer.whenTrue : pointer.whenFalse,
67718
+ scope,
67719
+ ctx
67720
+ );
67721
+ }
67722
+ case "delegateClosure" /* delegateClosure */:
67723
+ return {
67724
+ ...pointer.code === void 0 ? {} : { code: pointer.code },
67725
+ action: pointer.action,
67726
+ captures: pointer.captures.map(
67727
+ (capture) => evalPointer(capture, scope, ctx)
67728
+ ),
67729
+ ...ctx.thisValue === null || ctx.thisValue === void 0 ? {} : { [DELEGATE_LEXICAL_THIS]: ctx.thisValue },
67730
+ [DELEGATE_LEXICAL_ROOT]: ctx.rootValue
67731
+ };
67200
67732
  case "toBool" /* toBool */: {
67201
67733
  const v = evalPointer(pointer.pointer, scope, ctx);
67202
67734
  return jsTruthy(v);
@@ -67225,6 +67757,34 @@ function resolveEffectiveCallableMember(pointer, receiver, ctx) {
67225
67757
  const runtimeMember = resolveRuntimeSchemaMember(receiver, schemaKey, ctx);
67226
67758
  return runtimeMember ?? staticMember;
67227
67759
  }
67760
+ function assertValueEqualitySignature(member, receiver, ctx) {
67761
+ if (member.kind !== 13 /* Function */ && member.kind !== 23 /* NSFunction */) {
67762
+ throw new NSGetterRuntimeError(
67763
+ `Generic Equals resolved to non-callable member '${member.name}'.`
67764
+ );
67765
+ }
67766
+ const signature = resolveCallableSignature(member.id, member.kind, ctx);
67767
+ if (signature === null) {
67768
+ throw new NSGetterRuntimeError(
67769
+ `Generic Equals member '${member.name}' has no resolvable signature.`
67770
+ );
67771
+ }
67772
+ const runtimeSignature = substituteCallableSignatureForReceiver(
67773
+ signature,
67774
+ receiver,
67775
+ ctx
67776
+ );
67777
+ if (runtimeSignature.argumentTypes.length !== 1) {
67778
+ throw new NSGetterRuntimeError(
67779
+ `Generic Equals member '${member.name}' must take exactly one argument; found ${runtimeSignature.argumentTypes.length}.`
67780
+ );
67781
+ }
67782
+ if (runtimeSignature.returnTypeInfo.type !== 1 /* Bool */) {
67783
+ throw new NSGetterRuntimeError(
67784
+ `Generic Equals member '${member.name}' must return bool.`
67785
+ );
67786
+ }
67787
+ }
67228
67788
  function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver) {
67229
67789
  if (isNSDelegateClosureValueDraft(value)) {
67230
67790
  throw new NSGetterRuntimeError(
@@ -67234,6 +67794,7 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver) {
67234
67794
  if (isNSDelegateClosureValue(value)) {
67235
67795
  const closure = value;
67236
67796
  const thisValue = delegateClosureLexicalThis(closure, ctx);
67797
+ const captures = closure.captures ?? [];
67237
67798
  return executeCompiledFunction(
67238
67799
  value.action,
67239
67800
  {
@@ -67241,7 +67802,7 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver) {
67241
67802
  thisValue,
67242
67803
  rootValue: closure[DELEGATE_LEXICAL_ROOT] ?? ctx.rootValue
67243
67804
  },
67244
- args,
67805
+ [...args, ...captures],
67245
67806
  value.action.typeInfo.type === 0 /* Null */
67246
67807
  );
67247
67808
  }
@@ -112350,7 +112911,7 @@ var init_registry2 = __esm({
112350
112911
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
112351
112912
  formatVersion: 3,
112352
112913
  contractVersion: "3.13",
112353
- cliVersion: "0.32.4",
112914
+ cliVersion: "0.33.0",
112354
112915
  projectFileUploadBatchSize: 32,
112355
112916
  documentRecords: {
112356
112917
  member: {
@@ -118945,7 +119506,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
118945
119506
  async function main() {
118946
119507
  const args = parseArgs(process.argv.slice(2));
118947
119508
  if (args.command === "--version") {
118948
- console.log("0.32.4");
119509
+ console.log("0.33.0");
118949
119510
  return;
118950
119511
  }
118951
119512
  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.32.4",
3
+ "version": "0.33.0",
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.32.4 -->
12
+ <!-- reviewed-through-cli: 0.33.0 -->
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.32.4 -->
86
+ <!-- reviewed-through-cli: 0.33.0 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -8,7 +8,7 @@ runtime ownership, evaluation, and migrations.
8
8
  - Inline bodies and snippets
9
9
  - Nullability and ownership
10
10
  - Numeric builtins
11
- - Logical operators
11
+ - Conditional and logical operators
12
12
  - Loops and transfer
13
13
  - Switch
14
14
  - Try/catch
@@ -44,6 +44,20 @@ Prefer `--json` for automation.
44
44
 
45
45
  Inside an inline Class body, unqualified names resolve against that Class;
46
46
  invoke delegate and action members directly with `Selector()` or `OnChanged()`.
47
+ Clear every subscribed listener from a stored action with
48
+ `this.OnChanged.Clear()`. `Clear()` is a mutating statement and follows the
49
+ action member's effective storage and writability.
50
+
51
+ Inline `NeoDelegate` lambdas may be returned or stored and may read surrounding
52
+ locals and parameters. Those values are captured when the lambda is created,
53
+ so later changes to the outer binding do not alter the closure. Explicit lambda
54
+ parameter annotations accept the same type grammar as declarations, including
55
+ `decimal`, generic parameters, collections, arrays, and nullable types.
56
+
57
+ Calling `Equals(other)` on a non-null generic value dynamically uses the
58
+ runtime Class's one-argument `Equals` Function or NSFunction when it returns
59
+ `bool`, including the effective override. Values without that member fall back
60
+ to ordinary NeoScript equality.
47
61
 
48
62
  ## Nullability and ownership
49
63
 
@@ -113,7 +127,17 @@ enum, parameter, or local may shadow it. Qualified members use their owner's
113
127
  namespace and may be named `Math`; access one as `this.Math` or
114
128
  `SomeValue.Math` without colliding with the builtin qualifier.
115
129
 
116
- ## Logical operators
130
+ ## Conditional and logical operators
131
+
132
+ Use `condition ? whenTrue : whenFalse` for a value chosen by a condition:
133
+
134
+ ```neo
135
+ T modified = this.Modifier != null ? this.Modifier(value) : value;
136
+ ```
137
+
138
+ The operator has lower precedence than `??`, `||`, and `&&`, associates from
139
+ the right, and evaluates only the selected result arm. Both arms must share a
140
+ common assignable type or fit the surrounding expected type.
117
141
 
118
142
  Use `&&` and `||` with C# precedence: `&&` binds tighter than `||`, and
119
143
  parentheses preserve explicit grouping. Both operators short-circuit, so the