@neocompose/cli 0.88.0 → 0.89.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.88.0",
3
+ "version": "0.89.1",
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.88.0 -->
12
+ <!-- reviewed-through-cli: 0.89.1 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -109,7 +109,7 @@ The marker near the top of `SKILL.md` must exactly match the package version,
109
109
  and this quoted copy is checked too:
110
110
 
111
111
  ```html
112
- <!-- reviewed-through-cli: 0.88.0 -->
112
+ <!-- reviewed-through-cli: 0.89.1 -->
113
113
  ```
114
114
 
115
115
  Keep this skill on current behavior. Upgrade steps, rollout prerequisites,
@@ -0,0 +1,46 @@
1
+ # Extensions
2
+
3
+ Declare functions and computed properties in top-level extension blocks. Each
4
+ member has its own `@id`; the block has no identity. Blocks can share any
5
+ `.neo` definition file. Pull retains member file placement and otherwise uses
6
+ `Extensions/<Receiver>Extensions.neo`.
7
+
8
+ ```neo
9
+ extension Person {
10
+ @id("person-full-name")
11
+ public string FullName => $"{this.FirstName} {this.LastName}";
12
+ }
13
+
14
+ extension List<Item> {
15
+ @id("item-best")
16
+ Item? Best() => this.OrderByDescending((i) => i.Quality).FirstOrDefault();
17
+ }
18
+
19
+ extension<T> List<T> {
20
+ @id("list-first-value")
21
+ T? FirstValue => this.FirstOrDefault();
22
+ }
23
+ ```
24
+
25
+ Always use `this` to access the extended instance. Interface receivers are
26
+ supported and use the ordinary writable-member rules. Extensions add no
27
+ stored fields, constructors, inheritance, or operators. They are visible
28
+ project-wide. Applicable declared members take priority over extensions;
29
+ applicable extensions take priority over built-ins. Ambiguous candidates are
30
+ errors. Receiver type parameters must appear in the receiver and cannot have
31
+ defaults. Functions may also declare their own type parameters.
32
+
33
+ Computed properties support getters and explicit `set(Type value)` bodies.
34
+ Static extensions have no `this` and are called through the receiver type.
35
+ Extensions cannot be listener or spy/mock targets or use builder bodies.
36
+
37
+ Use `neo script check --all` to check local declarations. For direct checks,
38
+ select an extension with `--member <stable-member-id>`. Do not pass `--this`.
39
+ Direct evaluation accepts `--this-value <valueId>` only for a closed class or
40
+ interface receiver. Static extensions reject `--this-value`. Evaluate scalar,
41
+ collection, receiver-generic, and explicitly generic calls in an ordinary
42
+ `neo script eval --source '...'` expression. Evaluate setters through ordinary
43
+ assignments in source.
44
+
45
+ Both evaluators support extension members independently of the generated C#
46
+ version. See [Unity export](../sync/unity-export.md) for wrapper availability.
@@ -96,3 +96,43 @@ Put type arguments on the call, never the qualifier: `List<int>.Repeat` is an
96
96
  error and bare `List` is not a value. `count` must be an `int`; a negative
97
97
  count throws at runtime. The value and then the count evaluate exactly once,
98
98
  so a reference-typed value repeats the same reference.
99
+
100
+ ## Sequences and generators
101
+
102
+ Built-in collections implement `IEnumerable<T>` for their exact value type.
103
+ A dictionary enumerates its values. `GetEnumerator()` creates an independent
104
+ `IEnumerator<T>` with `MoveNext()`, `Current`, and `Dispose()`. Built-in cursors
105
+ snapshot membership when acquired. `Current` requires a successful `MoveNext`.
106
+
107
+ ```neo
108
+ public IEnumerable<int> Neighbors(int cell) {
109
+ yield return cell - 1;
110
+ yield return cell + 1;
111
+ }
112
+
113
+ public List<int> Collect(IEnumerable<int> values) => values;
114
+ public IEnumerator<int> Cursor(List<int> values) => values;
115
+ ```
116
+
117
+ Typed assignments, parameters, and returns acquire a cursor or collect a list
118
+ implicitly. Collecting a cursor consumes its remaining values, excluding its
119
+ current value, and disposes it. Element types must match exactly. A collection
120
+ itself does not own an iteration position.
121
+
122
+ A generator returns non-nullable `IEnumerable<T>` or `IEnumerator<T>`. Arguments
123
+ are evaluated when called; its body starts on the first `MoveNext`. Use
124
+ `yield return value` and `yield break`. `foreach` disposes its cursor on normal
125
+ completion, `break`, `return`, and catchable errors. A suspended generator runs
126
+ entered `finally` blocks when disposed. Yields cannot appear in catch/finally
127
+ blocks or a try block with catches; generators cannot invoke async functions.
128
+
129
+ `start..end` is an ascending, upper-exclusive int sequence. Reversed bounds are
130
+ empty. `foreach (var i in 0..count)` uses a counted loop without allocating a
131
+ cursor. Ranges require safe integer bounds.
132
+
133
+ `IEnumerator<T>` and derived cursor interfaces cannot occur in stored member
134
+ shapes, including Session storage, nested collection entries, or tuples. Use
135
+ computed properties and callable signatures for cursors; store `List<T>` data
136
+ explicitly. Stored enumerable interface members accept authored class rows.
137
+ Runtime sequences are shown as unenumerated and are not expanded for previews.
138
+ `ToSet()` rejects enumerable/cursor interface elements.
@@ -102,6 +102,7 @@ Read only the leaf the task needs:
102
102
  Related branches:
103
103
 
104
104
  - [References](../values/references.md): `Reference<T>` handles and slots.
105
+ - [Extensions](../declarations/extensions.md): typed receiver functions and computed properties.
105
106
  - [Overloads](../declarations/overloads.md): declaring and calling overloads.
106
107
  - [Constructors](../declarations/constructors.md) and
107
108
  [initializers](../declarations/initializers.md): construction order and
@@ -45,3 +45,19 @@ an export go into the next batch. It never pulls or pushes.
45
45
 
46
46
  Pull, push, branch, merge, resolve, init, and export take turns through
47
47
  `.neo/workspace.lock`; a lock whose process died is taken over.
48
+
49
+ ## C# target and extension wrappers
50
+
51
+ Set `codegen.csharpVersion` in `neo.json` to a numeric language version such as
52
+ `"14"`. `latest` and `preview` are rejected. Without an override, export uses
53
+ the supported Unity compiler mapping or the SDK's C# 9 baseline. It reads
54
+ `codegen.unityVersion` first, then the linked Unity project's
55
+ `ProjectSettings/ProjectVersion.txt`. `neo doctor` reports the effective target
56
+ and its source. An unrecognized Unity version produces a baseline warning.
57
+
58
+ Closed instance extension functions can generate classic C# extension methods.
59
+ Interface receivers, open receiver types, static extensions, and computed
60
+ extension properties require C# 14. Export omits an unavailable wrapper and
61
+ reports its member ID, receiver, and missing capability. It keeps the member
62
+ available to the NeoScript evaluators and SDK direct invocation API. Changing
63
+ the target regenerates wrappers and removes obsolete owned wrapper files.
@@ -26,7 +26,8 @@ public Reference<int> TrackedStat; // another member's int
26
26
 
27
27
  - `Reference<T>` also works as a parameter, collection element, dictionary
28
28
  value, and unbound generic argument (`Box<Reference<int>>`), and keeps that
29
- storage type through push and pull. Use `Set<T>` parameters for several
29
+ storage type through push and pull, including nested generic collections
30
+ and named tuples. Use `Set<T>` parameters for several
30
31
  selections.
31
32
  - `@settings(collection: Owner.Member)` binds a class reference to one
32
33
  collection. Unbound references select any stored row of `T` or a subclass,
@@ -47,20 +48,31 @@ public Reference<int> TrackedStat; // another member's int
47
48
 
48
49
  ```neo
49
50
  public Reference<Item> StarterSeed = root.Assets.Items.Index.Slug["seed.carrot"]!;
50
- public Reference<int> Gold = Reference(root.Player.Gold);
51
- public Reference<string> Greeting = Reference(root.Npcs.Index.Slug["mira"]!.Greeting);
52
- public Reference<NeoVariant<Tree>> Look = Reference(root.Garden.Felled);
51
+ public Reference<int> Gold = root.Player.Gold;
52
+ public Reference<string> Greeting = root.Npcs.Index.Slug["mira"]!.Greeting;
53
+ public Reference<NeoVariant<Tree>> Look = root.Garden.Felled;
53
54
  ```
54
55
 
55
56
  - An unbound handle default may name its row by a unique index: a `root`
56
57
  path to a List, `.Index.<Name>`, and a string literal key. Other shapes are
57
58
  initializer code.
58
- - Slot defaults wrap a member path in `Reference()`. A plain value is not a
59
- default, and `this` doesn't exist yet. A `NeoVariant<T>` member's own
60
- default stays the bare variant path.
59
+ - Slot defaults accept compatible stored member paths directly; `Reference()`
60
+ remains valid. Literals and computed values cannot supply a slot, and `this`
61
+ does not exist yet. A `NeoVariant<T>` member's own default stays the bare
62
+ variant path.
61
63
  - List and dictionary defaults may hold slot references, nested too:
62
- `List<Reference<int>> Counts = [Reference(root.Save.Bits)]`. Each instance
64
+ `List<Reference<int>> Counts = [root.Save.Bits]`. Each instance
63
65
  owns the collection; entries target the original slots.
66
+ - `Set<Reference<T>>` selections can mix bare paths and explicit references.
67
+ Computed selections deduplicate by target identity, not by current value.
68
+ A literal selection of fixed targets must not repeat the same target.
69
+ - A bare path to an existing reference copies its current target when the
70
+ object is constructed. Later rebinding of that source reference does not
71
+ rebind the copy.
72
+ - A nullable target still needs a stored row. Pin an otherwise absent
73
+ nullable member in its owner's initializer, for example
74
+ `new() { Maybe = null }`, before referencing it. A declaration default alone
75
+ does not create that row.
64
76
 
65
77
  ## Source forms
66
78
 
@@ -89,11 +101,18 @@ Reference<NeoDialogue>(id: "capitol-dialogue-id")
89
101
 
90
102
  ## Runtime semantics
91
103
 
92
- `Reference(place)` forms a reference to a stored member: `this.Gold`,
93
- `root.Player.Gold`, `item.Count`. Locals, parameters, list or dictionary
94
- elements, vector components, getters, query results, and new values have no
95
- row. When `Selected` is `Reference<Item>`, `Reference(this.Selected)` is
96
- `Reference<Reference<Item>>`. Access checks apply where it is formed.
104
+ When executable code expects `Reference<T>`, pass a compatible stored member
105
+ such as `this.Gold`, `root.Player.Gold`, or `item.Count` directly. This works
106
+ for constructor and function arguments, returns, typed locals, object
107
+ initializers, and collection entries. The reference keeps following that
108
+ slot when its value changes. `Reference(place)` remains valid when an
109
+ explicit reference is needed.
110
+
111
+ Locals, parameters, list or dictionary elements, vector components, getters,
112
+ query results, and new scalar values have no row. A narrowed nullable member
113
+ still names its declared storage type. When `Selected` is `Reference<Item>`,
114
+ `Reference(this.Selected)` is `Reference<Reference<Item>>`. Access checks
115
+ apply where the reference is formed.
97
116
 
98
117
  ```neo
99
118
  this.A = 5; // writes 5 into A's target
@@ -106,8 +125,8 @@ this.A += 1; // reads A's target, writes it back
106
125
  - Assigning a same-typed reference rebinds; anything else writes through.
107
126
  `this.A = c ? first : second` rebinds when both arms are references, writes
108
127
  when both are scalars, and reads the chosen target when mixed.
109
- - A plain value never becomes a reference: passing `3` to a `Reference<int>`
110
- parameter is an error.
128
+ - A value without a stored slot never becomes a reference: passing `3` to a
129
+ `Reference<int>` parameter is an error.
111
130
  - Constructors may initialize their own reference fields but can't write
112
131
  through external targets.
113
132
  - `==` compares handles by identity and slots by value. Collections of
@@ -138,6 +157,10 @@ Lookup assignment still needs an existing backing row.
138
157
  (`root.Assets.Quests.Daily` or `Reference(root.Assets.Quests.Daily)`) and
139
158
  follow the same rules. The path may cross construction defaults and
140
159
  declarations from the same push; no preliminary push or manual ID is needed.
160
+ Pull emits bare paths in `collectionValue` settings when the target is reachable
161
+ from a root; otherwise it retains `Reference(id: "...")`. Lookup variant folder
162
+ bindings and `.Bind(root.Assets.Items.Index.Slug["seed.carrot"]!)` use the same
163
+ implicit reference conversion.
141
164
  Invalid reference settings are errors, never silently dropped.
142
165
 
143
166
  ## Init-built objects
@@ -145,7 +168,7 @@ Invalid reference settings are errors, never silently dropped.
145
168
  An object `Root.neo` writes is init-built when its class has an `init` block.
146
169
  A declaration binding may name a member inside it, and push stores that
147
170
  member's row. A reference captured by `init` or a constructor
148
- (`new Rule(Reference(this.Weather))`) keeps its target's row. Status refuses a
171
+ (`new Rule(this.Weather)`) keeps its target's row. Status refuses a
149
172
  value row's reference to a member of an init-built object (bind it from a
150
173
  declaration instead) and a binding to a class or list that an `init` block
151
174
  builds.
@@ -86,7 +86,7 @@ class root.
86
86
  NeoLookupVariantFolder<ExampleObject, NeoSortingLayer> Root = new(
87
87
  path: "",
88
88
  collection: Assets.SortingLayers,
89
- collectionValue: Reference(root.Assets.SortingLayers),
89
+ collectionValue: root.Assets.SortingLayers,
90
90
  );
91
91
 
92
92
  @id("736dddf8-2881-47e1-a1d3-55fb65ea9f2a")
@@ -97,13 +97,14 @@ NeoLookupVariant<ExampleObject, NeoSortingLayer> Layered = new(
97
97
  ```
98
98
 
99
99
  - `collection` names the eligible list member; `collectionValue` the stored
100
- list whose rows it enumerates. Prefer the `Reference(root...)` form pull
100
+ list whose rows it enumerates. Prefer the bare `root...` path pull
101
101
  emits.
102
102
  - Lookup `initialize` receives the row; lookup `apply` receives the object,
103
103
  then the row.
104
104
  - A `NeoLookupVariant<TObject, TValue>` member holds an unbound handle. A
105
105
  plain `NeoVariant<TObject>` member may pick one row with
106
- `.Bind(Reference(...))`; never bind a row to the lookup-handle type.
106
+ `.Bind(root.Assets.Items.Index.Slug["seed.carrot"]!)`; explicit
107
+ `Reference(...)` remains valid. Never bind a row to the lookup-handle type.
107
108
 
108
109
  ## Type restrictions
109
110