Skip to content

Conversation

@krodak
Copy link
Member

@krodak krodak commented Feb 4, 2026

Overview

Extends associated value enum support to accept all types already supported in struct fields and arrays: @JS struct, @JS class, JSObject, nested associated value enums, and arrays - including their optional variants.

Previously, associated values in exported enums were limited to primitive types (String, Int, Bool, Float, Double) and simple enums (case/raw value). This PR closes that gap so that any type usable as a struct field can also be used as an enum associated value.

Supported types examples

@JS struct Point { var x: Double; var y: Double }
@JS class User { var name: String; ... }

@JS enum AllTypesResult {
    case structPayload(Point)
    case classPayload(User)
    case jsObjectPayload(JSObject)
    case nestedEnum(APIResult)
    case arrayPayload([Int])
    case empty
}

@JS enum OptionalResult {
    case optStruct(Point?)
    case optClass(User?)
    case optJSObject(JSObject?)
    case optNestedEnum(APIResult?)
    case optArray([Int]?)
    case empty
}

Technical Changes

Enum helper factory scoping

Enum helper factories (lower/lift closures) are now generated inside createInstantiator alongside struct helpers, giving them access to structHelpers, enumHelpers, tmpParamPointers, tmpRetPointers, and class exports via _exports['ClassName']. Previously they were at module scope, which would cause issues when referencing class constructors or struct/enum helpers.

Update to tmpRetTag single-register to avoid overwrite for nested enums

tmpRetTag was a single scalar register, not a stack. When an associated value enum contained another associated value enum, the inner enum's _swift_js_push_tag() overwrote the outer enum's tag, causing the JS lift function to misinterpret the case.

Fix: Changed tmpRetTag from a scalar to a stack (array with push/pop), matching the other typed stacks. The lift function's first parameter was changed from the array reference to a pre-popped scalar tag, allowing both top-level returns (tmpRetTag.pop()) and protocol/closure contexts (already-extracted scalar) to use the same lift function.

Additionally, generateReturnSwitchCases in ExportSwift.swift now pushes the tag after payloads (not before). This is required for correctness with the stack-based tmpRetTag: since stacks use LIFO ordering, the outermost tag must be pushed last so it is popped first. For example, when lowering AllTypesResult.nestedEnum(APIResult.success("nested!")):

  • Old order (tag before payloads): outer tag 3 pushed → inner enum lowered (pushes inner tag 0) → stack is [3, 0]. JS pops and gets 0 first (wrong — that's the inner tag).
  • New order (tag after payloads): inner enum lowered first (pushes inner tag 0 + payload) → outer tag 3 pushed last → stack is [0, 3]. JS pops and gets 3 first (correct outer tag), then when lifting the nested enum payload, pops 0 (correct inner tag).

Refactoring: unified stack lower/lift fragments

Mostly unified code paths for stack-based types in JSGlueGen, as they handled the same BridgeType → stack push/pop logic with near-identical implementations:

Tests

  • Snapshot tests: New AllTypesResult and OptionalAllTypesResult enum definitions in the snapshot input, with updated codegen and link snapshots.
  • E2E tests: Round-trip tests for all five new types (struct, class, JSObject, nested enum, array) and their optional variants, verifying correct serialization and deserialization across the Swift/JS boundary.

Documentation

Exporting Swift Enum doc reflects changes and includes new supported types

@krodak krodak force-pushed the krodak/associated-enum-alignment branch from b06dc3a to dcc148f Compare February 4, 2026 12:50
Copy link
Member

@kateinoigakukun kateinoigakukun left a comment

Choose a reason for hiding this comment

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

Thanks!

@krodak krodak merged commit 26dfded into swiftwasm:main Feb 4, 2026
11 checks passed
@krodak krodak deleted the krodak/associated-enum-alignment branch February 4, 2026 13:25
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.

2 participants