Skip to content

Commit 89a080c

Browse files
committed
Document serialization compiler intrinsics
1 parent c1a2272 commit 89a080c

2 files changed

Lines changed: 72 additions & 61 deletions

File tree

_doc/manual.md

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -880,9 +880,10 @@ function foo()
880880

881881
### Compiler-assisted field mapping
882882

883-
For dedicated state classes, import `MagicFunctions` to use compiler-assisted field iteration and generic
884-
construction. These operations expand to ordinary constructors and direct field accesses; serialization formats,
885-
hashes, and storage remain standard-library concerns.
883+
Import `MagicFunctions` to use the compiler-provided `wurstForFields`, `wurstMapFields`, and
884+
`wurstNewInstance<T>()` helpers. Their `@compilerintrinsic` declarations provide completion, hover information,
885+
and definition navigation. Calls are replaced at compile time with ordinary field accesses or constructor calls;
886+
the declarations themselves do not remain in generated Jass or Lua.
886887

887888
```wurst
888889
import MagicFunctions
@@ -892,59 +893,71 @@ class PlayerState
892893
string name = ""
893894
894895
function save(FieldWriter writer)
895-
forFields((fieldName, value) -> writer.write(fieldName, value))
896+
wurstForFields((fieldName, value) -> writer.write(fieldName, value))
896897
897898
function load(FieldReader reader)
898-
mapFields((fieldName, value) -> reader.read(fieldName, value))
899+
wurstMapFields((fieldName, value) -> reader.read(fieldName, value))
899900
```
900901

901-
`forFields` invokes the callback once for every accessible, non-static instance field. This includes inherited,
902-
module-injected, readonly, and constant fields. The callback receives the field key and current value and must
903-
produce a statement. `mapFields` assigns each callback result back to its field, so it includes only accessible,
904-
mutable instance fields. Module field keys are qualified when necessary to disambiguate equal names.
902+
`wurstForFields` emits one callback invocation for each accessible, non-static instance field. This includes
903+
inherited, module-injected, readonly, and constant fields. The callback must produce a statement.
904+
`wurstMapFields` assigns each callback result back to its field, so every visited field must be mutable; a readonly
905+
or constant field produces a compile-time diagnostic. Static fields are never visited.
905906

906-
Both functions also accept an explicit target. The target is evaluated exactly once:
907+
The callback receives a field key and the field's current value. The value parameter has a different concrete type
908+
for each generated invocation, despite the `int` placeholder shown by the tooling interface. Leave callback
909+
parameter types inferred and use overloads for the field types your mapper supports. Field iteration itself is
910+
shallow: nested classes, tuples, collections, nullable values, and other composite types require matching library
911+
or user-provided overloads. The compiler does not recursively serialize them.
912+
913+
Both operations also accept an explicit target as their first argument. Class targets are evaluated exactly once.
914+
Class and tuple targets are supported. A tuple passed to `wurstMapFields` must be a variable so the compiler can
915+
write the mapped tuple back once after updating its components.
907916

908917
```wurst
909-
forFields(state, (fieldName, value) -> writer.write(fieldName, value))
910-
mapFields(state, (fieldName, value) -> reader.read(fieldName, value))
911-
```
918+
tuple Position(int x, int y)
919+
920+
function savePosition(Position position, FieldWriter writer)
921+
wurstForFields(position, (fieldName, value) -> writer.write(fieldName, value))
912922
913-
Leave callback parameter types inferred and overload the reader or writer for every field type used by the state
914-
class. An applicable ordinary visible overload with one of these names is resolved normally and is not treated as
915-
compiler magic.
923+
function loadPosition(Position position, FieldReader reader) returns Position
924+
var result = position
925+
wurstMapFields(result, (fieldName, value) -> reader.read(fieldName, value))
926+
return result
927+
```
916928

917-
Use `newInstance<T>()` when a specialized generic function needs to construct its concrete result type:
929+
Use `wurstNewInstance<T>()` in a generic loader when the concrete result type is known at specialization time:
918930

919931
```wurst
920932
function loadState<T:>(FieldReader reader) returns T
921-
let result = newInstance<T>()
922-
mapFields(result, (fieldName, oldValue) -> reader.read(fieldName, oldValue))
933+
let result = wurstNewInstance<T>()
934+
wurstMapFields(result, (fieldName, oldValue) -> reader.read(fieldName, oldValue))
923935
return result
924936
```
925937

926-
At each concrete call such as `loadState<PlayerState>(reader)`, the compiler specializes the required path and
927-
lowers `newInstance<PlayerState>()` to its normal zero-argument constructor. `T` must resolve to a concrete,
928-
non-abstract class with an accessible zero-argument constructor. Interfaces, handles, primitives, tuples,
929-
unresolved type parameters, and classes without a usable constructor are rejected.
930-
931-
Keep a generic loader in the free-function form shown above. On Lua, a method cannot currently combine type
932-
parameters from its generic owning class with additional type parameters declared by the method itself.
933-
Likewise, do not call `newInstance<T>()` from a generic class constructor. Construct the state in the loader and
934-
initialize nested state explicitly afterward.
935-
936-
On Lua, do not invoke a generic-construction method directly on a freshly constructed generic receiver. Prefer the
937-
free loader above, or store the receiver in a typed local first. Multi-parameter generic-interface dispatch is also
938-
outside this loader contract; use one construction type parameter. `newInstance<T>()` is for runtime Jass/Lua
939-
construction and is not supported inside `compiletime(...)` expressions. Field mapping also does not support
940-
nested modules whose sibling submodules declare fields with the same name; use direct fields, inheritance, or
941-
unique shallow module field names for dedicated state classes.
942-
943-
These are compile-time transformations, not runtime reflection, and generate equivalent direct accesses in both
944-
Jass and Lua. They generate no runtime registry, type-name lookup, or reflection metadata. Keep serializable state
945-
in small, dedicated classes, avoid unsupported field kinds such as static fields, and keep persistence codecs and
946-
format migration separate from the state model. See the [Save and Load tutorial](/tutorials/saveload.html) for
947-
integration with Warcraft III's file API.
938+
The helper invokes the normal accessible zero-argument constructor of a concrete, non-abstract class. It does not
939+
allocate an uninitialized object or look up a class by name. Constructor initializers run normally, which lets a
940+
serialization library retain defaults for fields missing from older records.
941+
942+
These helpers provide no wire format, stable field IDs, versioning, migration policy, integrity checks, runtime
943+
reflection metadata, or type registry. Those remain library concerns. In particular, field keys are source names,
944+
not stable persisted identities; a library must translate them to its own schema identity if renames need to remain
945+
compatible.
946+
947+
The original unprefixed `forFields`, `mapFields`, and `newInstance<T>()` spellings remain available as compatibility
948+
fallbacks. New code should use the `wurst`-prefixed names to avoid accidental collisions. If an applicable ordinary
949+
function with the same name is visible, normal overload resolution selects that function instead of the compiler
950+
operation.
951+
952+
For Lua, keep generic construction in a free function with one construction type parameter. Do not combine type
953+
parameters from a generic owning class with independent method type parameters, call `wurstNewInstance<T>()` from
954+
a generic class constructor, invoke a generic-construction method directly on a freshly constructed generic
955+
receiver, or use multi-parameter generic-interface dispatch. `wurstNewInstance<T>()` is a runtime Jass/Lua helper
956+
and is not supported inside `compiletime(...)`. Field mapping does not support nested modules whose sibling
957+
submodules declare equal field names.
958+
959+
See the [Save and Load tutorial](/tutorials/saveload.html) for integration with Warcraft III's file API and the
960+
standard library serialization layers.
948961

949962
Identifiers beginning with `__wurst` are reserved for compiler-generated internals and must not be declared by
950963
user code.

_tutorials/saveload.md

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ The `serialize()` function returns a `ChunkedString`, which then can be passed t
6464

6565
## Compiler-assisted field mapping
6666

67-
For small, dedicated state classes, Wurst can generate the repetitive field mapping for you. Import
68-
`MagicFunctions`, then use `forFields` when writing fields and `mapFields` when reading them:
67+
For small, dedicated state classes, Wurst can generate repetitive field mapping. Import `MagicFunctions`, then
68+
use the canonical `wurstForFields` helper when writing and `wurstMapFields` when reading:
6969

7070
```wurst
7171
import MagicFunctions
@@ -75,49 +75,47 @@ class PlayerState
7575
string name = ""
7676
7777
function save(FieldWriter writer)
78-
forFields((fieldName, value) -> writer.write(fieldName, value))
78+
wurstForFields((fieldName, value) -> writer.write(fieldName, value))
7979
8080
function load(FieldReader reader)
81-
mapFields((fieldName, value) -> reader.read(fieldName, value))
81+
wurstMapFields((fieldName, value) -> reader.read(fieldName, value))
8282
```
8383

8484
The callback receives the field name as a `string` and the current field value. The compiler expands these
8585
calls into ordinary direct field accesses, so there is no runtime reflection or metadata lookup. The same
8686
source works for both Jass and Lua.
8787

88-
`forFields` is for statement callbacks. It includes accessible non-static fields, including inherited,
89-
module-injected, readonly, and constant state. `mapFields` uses the callback result to assign each field, so the
90-
reader should return the value to store; readonly and constant fields are therefore excluded from mapping. Leave
91-
both callback parameter types inferred and use a reader/writer overload for each field type. Module field keys are
92-
qualified when equal names need disambiguation.
88+
`wurstForFields` visits accessible non-static fields, including inherited, module-injected, readonly, and constant
89+
state. `wurstMapFields` assigns each callback result back, so all visited fields must be mutable. Leave both callback
90+
parameter types inferred and provide reader/writer overloads for every field type. Mapping is shallow: nested
91+
objects, tuples, collections, and other composite values need a matching library or user-provided codec.
9392

9493
The explicit-target forms work outside the state class and evaluate the target exactly once:
9594

9695
```wurst
97-
forFields(state, (fieldName, value) -> writer.write(fieldName, value))
98-
mapFields(state, (fieldName, value) -> reader.read(fieldName, value))
96+
wurstForFields(state, (fieldName, value) -> writer.write(fieldName, value))
97+
wurstMapFields(state, (fieldName, value) -> reader.read(fieldName, value))
9998
```
10099

101-
For a generic load wrapper, `newInstance<T>()` constructs the specialized concrete class through its normal
100+
For a generic load wrapper, `wurstNewInstance<T>()` constructs the specialized concrete class through its normal
102101
accessible zero-argument constructor:
103102

104103
```wurst
105104
function loadState<T:>(FieldReader reader) returns T
106-
let state = newInstance<T>()
107-
mapFields(state, (fieldName, oldValue) -> reader.read(fieldName, oldValue))
105+
let state = wurstNewInstance<T>()
106+
wurstMapFields(state, (fieldName, oldValue) -> reader.read(fieldName, oldValue))
108107
return state
109108
```
110109

111110
This works for Jass and Lua without runtime reflection, a type registry, or a type-id switch. `T` must resolve to a
112111
concrete, non-abstract class with an accessible zero-argument constructor. Keep state classes focused on data and
113-
keep the persistence codec, schema versioning, validation, and migrations separate from construction and field
114-
mapping. Applicable ordinary visible overloads named `forFields`, `mapFields`, or `newInstance` still resolve
115-
normally. Keep the generic loader as a free function: on Lua, a method cannot currently combine type parameters
116-
from its generic owning class with additional type parameters declared by the method itself. Do not call
117-
`newInstance<T>()` from a generic class constructor; construct the state in the loader and initialize nested state
112+
keep the persistence codec, stable field identity, schema versioning, validation, and migrations separate from
113+
construction and field mapping. Keep the generic loader as a free function: on Lua, a method cannot currently
114+
combine type parameters from its generic owning class with additional type parameters declared by the method itself. Do not call
115+
`wurstNewInstance<T>()` from a generic class constructor; construct the state in the loader and initialize nested state
118116
explicitly afterward. Avoid calling generic-construction methods directly on freshly constructed generic receivers
119117
on Lua, and keep the loader to one construction type parameter rather than multi-parameter generic-interface
120-
dispatch. `newInstance<T>()` is not supported inside `compiletime(...)` expressions. Dedicated state classes should
118+
dispatch. `wurstNewInstance<T>()` is not supported inside `compiletime(...)` expressions. Dedicated state classes should
121119
also avoid nested modules with sibling fields sharing the same name; prefer direct fields or ordinary inheritance.
122120

123121

0 commit comments

Comments
 (0)