Skip to content

Add compile-time field iteration for serialization - #1219

Open
Frotty wants to merge 2 commits into
masterfrom
codex/derive-serialization
Open

Add compile-time field iteration for serialization#1219
Frotty wants to merge 2 commits into
masterfrom
codex/derive-serialization

Conversation

@Frotty

@Frotty Frotty commented Aug 8, 2026

Copy link
Copy Markdown
Member

What changed

Adds two compile-time-expanded field operations for instance methods and constructors:

__wurst_forFields((fieldName, value) -> writer.write(fieldName, value))
__wurst_mapFields((fieldName, value) -> reader.read(fieldName, value))

__wurst_forFields emits one direct callback expression per instance field. __wurst_mapFields emits one direct assignment per field using the callback result. Static fields are excluded and declaration order is preserved. The __wurst_ spelling makes the compiler intrinsic explicit and leaves ordinary user-defined forFields/mapFields functions untouched.

Why

Wurst save/load libraries currently require handwritten serialization and deserialization code for every field. Runtime reflection would add metadata and lookup overhead on Warcraft III targets. Expanding field operations before type checking gives reflection-like ergonomics while preserving overload resolution and producing the same direct field accesses as handwritten code.

This is intentionally a narrow draft/prototype so the API and limitations can be discussed before broadening it (for example inheritance, module fields, opt-out annotations, or schema/version behavior).

Impact

  • Format-agnostic: stdlib or user code chooses the writer/reader and encoding.
  • Type-safe: generated calls go through normal overload resolution.
  • Backend-neutral: expansion occurs before Jass/Lua lowering.
  • Zero runtime reflection tables, iteration, closures, or dynamic lookup.

Validation

  • Added focused execution and diagnostic tests.
  • Verified Jass and real Lua execution.
  • Verified generated Lua contains direct typed calls and assignments and no field intrinsics.
  • Added regression coverage for ordinary helper-name compatibility and nested closure shadowing.
  • Full Gradle test suite passes (./gradlew test, 8m40s).
  • git diff --check passes.

@Frotty
Frotty marked this pull request as ready for review August 8, 2026 08:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9e2100348

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +44 to +46
String name = call.getFuncName();
if ("forFields".equals(name) || "mapFields".equals(name)) {
calls.add(call);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid hijacking user-defined field helper calls

Any existing package that defines a regular unqualified forFields or mapFields function now has every call collected before name resolution. For example, function forFields(int x) ... followed by forFields(1) is reported as being outside an instance context instead of resolving to the user function, introducing a source-breaking global reservation of two ordinary identifiers. Restrict the rewrite to explicit intrinsic syntax or markers so resolvable user calls remain valid.

Useful? React with 👍 / 👎.

Comment on lines +112 to +115
public void visit(ExprVarAccess access) {
super.visit(access);
if (access.getVarName().equals(nameParameter) || access.getVarName().equals(valueParameter)) {
accesses.add(access);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve bindings when substituting closure parameters

When the callback body contains a nested closure whose parameter reuses name or value, this visitor descends into that closure and replaces its bound ExprVarAccess solely by spelling. For example, forFields((name, value) -> consume((int value) -> value)) silently changes the inner closure to return this.<field> rather than its argument for every field, miscompiling valid code; make substitution scope-aware and do not replace references shadowed by nested declarations.

Useful? React with 👍 / 👎.

@Test
public void serializesAndDeserializesFieldsWithoutRuntimeReflection() throws IOException {
test()
.testLua(true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exercise the feature test on Jass too

This test calls .testLua(true), whose setter also sets luaOnly = true in WurstScriptTest, so the harness skips every Jass translation/interpreter variant. The new compiler feature therefore has no committed regression coverage on Jass; enable Lua without luaOnly or add a separate Jass execution test.

AGENTS.md reference: AGENTS.md:L215-L221

Useful? React with 👍 / 👎.

Frotty commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d78b456b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +82 to +83
int statementIndex = statements.indexOf(call);
statements.remove(statementIndex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve callback parameter diagnostics during expansion

When either placeholder has an explicit type, removing the call also removes its WShortParameter nodes before validation, so invalid annotations are silently accepted. For example, __wurst_mapFields((NoSuch name, NoSuch value) -> value) compiles and runs despite both referenced types being undefined. Validate these declarations or reject explicit annotations before discarding the closure so ordinary type diagnostics are not bypassed.

AGENTS.md reference: AGENTS.md:L84-L85

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant