Skip to content

feat: add discriminated union types to nitrogen - #1590

Open
dhruvpatel374 wants to merge 3 commits into
margelo:mainfrom
dhruvpatel374:feat/discriminated-union-types
Open

feat: add discriminated union types to nitrogen#1590
dhruvpatel374 wants to merge 3 commits into
margelo:mainfrom
dhruvpatel374:feat/discriminated-union-types

Conversation

@dhruvpatel374

Copy link
Copy Markdown

Closes #1466

What

Adds support for discriminated union types in nitrogen. When a TypeScript union consists of interfaces that share a common string literal property, nitrogen now detects the discriminant key and generates a named JSIConverter that dispatches on it at runtime instead of falling back to positional std::variant indexing.

interface Truck { kind: 'truck'; payload: number }
interface Boat  { kind: 'boat';  lengthMeters: number }
type Vehicle = Truck | Boat

Here kind is the discriminant. At runtime, if kind is "truck" the converter deserializes a Truck, if "boat" it deserializes a Boat. This also solves the ambiguous-all-nulls problem from the issue - even when every other property is optional/null, the discriminant tells the converter exactly which struct to construct.

How it works

  • createType.ts - added findDiscriminantKey() that inspects a union's constituents for a shared property where every member has a unique string literal value. If found, returns a DiscriminatedUnionType instead of a plain VariantType.
  • getInterfaceProperties.ts - added optional skipKeys param so the discriminant property is excluded from the struct fields (the union's JSIConverter handles dispatch, the struct itself doesn't need it).
  • CppDiscriminatedUnion.ts - generates a JSIConverter<std::variant<A, B>> specialization that reads the discriminant key first, then delegates to each struct's own converter.
  • SwiftDiscriminatedUnion.ts - generates a @frozen enum with cases named after discriminant values (e.g. .truck, .boat) instead of positional .first, .second.
  • KotlinDiscriminatedUnion.ts - generates a sealed class with inner classes named after discriminant values.
  • Wired up in SwiftCxxBridgedType, KotlinCxxBridgedType, SwiftCxxTypeHelper, and getReferencedTypes.

Generated output

C++ (Vehicle.hpp):

template <>
struct JSIConverter<std::variant<Truck, Boat>> final {
  static inline std::variant<Truck, Boat> fromJSI(...) {
    // reads "kind", switches on hashString, delegates to Truck or Boat converter
  }
};

Swift (Vehicle.swift):

@frozen public indirect enum Vehicle {
  case truck(Truck)
  case boat(Boat)
}

Kotlin (Vehicle.kt):

sealed class Vehicle {
  data class Truck(val value: Truck): Vehicle()
  data class Boat(val value: Boat): Vehicle()
}

Test

  • Added Truck, Boat, Vehicle (discriminant key kind) to TestObject.nitro.ts
  • Added bounceVehicle method to C++, Swift, and Kotlin test implementations
  • Added runtime assertions in getTests.ts covering both the truck and boat cases

Checklist

  • bun specs run and generated files committed
  • bun lint-all passes
  • bun typecheck passes
  • No existing specs or test cases removed
  • Diff is scoped to this feature only

@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
nitro-docs Skipped Skipped Sep 5, 2026 2:50pm UTC

Request Review

@dhruvpatel374
dhruvpatel374 force-pushed the feat/discriminated-union-types branch from 1efc067 to 16d5cbe Compare September 5, 2026 14:50
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.

Add Discriminating Types

1 participant