# How curryhoward Handles Case Classes and Sealed Traits for Code Generation

> Learn how CurryHoward leverages Scala reflection to generate code from case classes and sealed traits, translating them into algebraic types and emitting idiomatic Scala.

- Repository: [Chymyst/curryhoward](https://github.com/chymyst/curryhoward)
- Tags: how-to-guide
- Published: 2026-02-27

---

**CurryHoward uses Scala reflection to detect case classes and sealed traits, translates them into algebraic types (`NamedConjunctT` for products and `DisjunctT` for sums), and generates idiomatic Scala code through a theorem prover and macro-based emitter.**

The curryhoward library automates Scala code generation by treating types as logical propositions and programs as proofs. When working with **curryhoward case classes sealed traits code generation**, the macro system must accurately reflect on Scala's algebraic data structures to produce correct, type-safe implementations.

## Detecting and Representing Case Classes

The macro entry point `Macros.buildTypeExpr` in [`src/main/scala/io/chymyst/ch/Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/src/main/scala/io/chymyst/ch/Macros.scala) inspects type symbols to distinguish between case classes, case objects, and other types.

### Case Classes as NamedConjunctT

When the macro encounters a case class, it collects the accessor methods to extract field names and types:

```scala
if (finalTypeSymbol.asClass.isCaseClass) {
  // collect accessor methods (case fields)
  val (accessors, typeExprs) = finalType.decls
    .collect { case s: MethodSymbol if s.isCaseAccessor ⇒
      val accessorType = buildTypeExpr(s.typeSignature.resultType, Seq(), typesSeenNow)
      val substitutedType = TypeExpr.substNames(accessorType, typeMap.toMap)
      (s.name.decodedName.toString, substitutedType) }
    .toList.unzip
  val wrapped = if (typeExprs.isEmpty) List(UnitT(typeName)) else typeExprs
  NamedConjunctT(typeName, matchedTypeArgs, accessors, wrapped)
}

```

*Source*: [[`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) – lines 95‑108](https://github.com/chymyst/curryhoward/blob/master/src/main/scala/io/chymyst/ch/Macros.scala#L95-L108)

The `NamedConjunctT` type expression stores the constructor name, type parameters, field names (`accessors`), and field types (`wrapped`). This representation allows the theorem prover to treat case classes as product types while preserving the specific constructor information needed for code generation.

### Case Objects and Empty Argument Lists

For case objects, the macro detects `isModuleClass` and creates a `NamedConjunctT` with empty accessor and wrapped lists. Empty-argument case classes contain a single `UnitT` in their wrapped list. These cases are handled in [`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) and verified in the test suite.

*Source*: [[`MacrosSpec.scala`](https://github.com/chymyst/curryhoward/blob/main/MacrosSpec.scala) – lines 93‑100](https://github.com/chymyst/curryhoward/blob/master/src/test/scala/io/chymyst/ch/unit/MacrosSpec.scala#L93-L100)

## Handling Sealed Traits as Sum Types

Sealed traits and abstract classes with case class implementations represent sum types (disjunctions) in the curryhoward system.

### DisjunctT for Sealed Traits

When `buildTypeExpr` encounters a sealed trait, it validates that all direct subclasses are case classes or case objects, then constructs a `DisjunctT`:

```scala
if ((typeSymbol.asClass.isTrait || typeSymbol.asClass.isAbstract) &&
    subclasses.nonEmpty &&
    subclasses.forall { s ⇒
      val resultClass = s.typeSignature.resultType.typeSymbol.asClass
      resultClass.isCaseClass || resultClass.isModuleClass
    }) {
  // Build a DisjunctT from each subclass
  subclasses.map { s ⇒
    val subclassType = buildTypeExpr(s.asType.toType, Seq(), typesSeenNow)
    // handle type‑parameter substitution for GADTs …
    if (subclassType.typeParams.nonEmpty) { … }
    else subclassType
  } match {
    case part :: Nil => part
    case parts       => DisjunctT(typeName, matchedTypeArgs, parts.asInstanceOf[List[NamedConjunctT]])
  }
}

```

*Source*: [[`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) – lines 126‑166](https://github.com/chymyst/curryhoward/blob/master/src/main/scala/io/chymyst/ch/Macros.scala#L126-L166)

The `DisjunctT` type expression captures the sealed trait name and a list of `NamedConjunctT` representing each case class alternative.

### GADT Support and Type Parameter Substitution

The macro handles Generalized Algebraic Data Types (GADTs) by substituting type parameters when building the `DisjunctT`. If a subclass has different type parameters than the parent trait, the macro maps them correctly to ensure type safety in the generated code.

## From TypeExpr to Generated Scala Code

The transformation from algebraic type representation to executable Scala involves two phases: theorem proving and code emission.

### Theorem Prover and Term Synthesis

Once `buildTypeExpr` constructs the `TypeExpr` (whether `NamedConjunctT` or `DisjunctT`), the system passes it to the theorem prover in [`TheoremProver.scala`](https://github.com/chymyst/curryhoward/blob/main/TheoremProver.scala). The prover searches for a `TermExpr` that inhabits the type, treating the algebraic representation as a logical formula to be proved.

### emitTermCode and Code Emission

The `emitTermCode` method in [`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) converts the synthesized `TermExpr` back into Scala AST nodes:

- **`NamedConjunctE`** generates constructor calls like `Person(name, age)`
- **`DisjunctE`** generates pattern matches with case class extraction

*Source*: [[`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) – `emitTermCode`](https://github.com/chymyst/curryhoward/blob/master/src/main/scala/io/chymyst/ch/Macros.scala)

## Practical Examples

### Simple Case Class Extraction

```scala
case class Wrap1[A, B](x: Int, a: A, b: B)
def f[A, B]: Wrap1[A, B] ⇒ B = implement

```

The macro generates:

```scala
def f[A, B](w: Wrap1[A, B]): B = w.b

```

*Source*: [[`LJTSpec3.scala`](https://github.com/chymyst/curryhoward/blob/main/LJTSpec3.scala) – lines 40‑44](https://github.com/chymyst/curryhoward/blob/master/src/test/scala/io/chymyst/ch/unit/LJTSpec3.scala#L40-L44)

### Sealed Trait Pattern Matching

```scala
sealed trait GadtChoice[A, B]
case class GadtChoice1[A, B](x: A, a: A, b: B) extends GadtChoice[A, B]
case class GadtChoice2[B](name: String, bb: B) extends GadtChoice[Boolean, B]

def f[A, B]: GadtChoice[A, B] ⇒ B = implement

```

Generated code:

```scala
def f[A, B](g: GadtChoice[A, B]): B = g match {
  case GadtChoice1(_, _, b) => b
  case GadtChoice2(_, bb)   => bb
}

```

*Source*: [[`LJTSpec3.scala`](https://github.com/chymyst/curryhoward/blob/main/LJTSpec3.scala) – lines 46‑52](https://github.com/chymyst/curryhoward/blob/master/src/test/scala/io/chymyst/ch/unit/LJTSpec3.scala#L46-L52)

## Summary

- **Type Reflection**: The macro in [`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) uses Scala reflection to detect case classes (`isCaseClass`), case objects (`isModuleClass`), and sealed traits with case class subclasses.
- **Algebraic Mapping**: Case classes become `NamedConjunctT` (product types) while sealed traits become `DisjunctT` (sum types) in the internal `TypeExpr` hierarchy.
- **GADT Support**: The system handles Generalized Algebraic Data Types by substituting type parameters when building `DisjunctT` representations.
- **Code Generation**: The theorem prover synthesizes a `TermExpr` that inhabits the type, which `emitTermCode` converts to idiomatic Scala constructor calls or pattern matches.
- **Edge Cases**: Empty case classes and case objects are handled via `UnitT` and empty accessor lists, ensuring complete coverage of Scala's algebraic data type syntax.

## Frequently Asked Questions

### How does curryhoward distinguish between case classes and regular classes?

The macro checks `finalTypeSymbol.asClass.isCaseClass` to identify case classes specifically. Regular classes without the case modifier are not supported for automatic code generation because they lack the guaranteed accessor methods and stable construction patterns that the theorem prover relies on to build `NamedConjunctT` representations.

### Can curryhoward handle nested case classes or recursive algebraic data types?

Yes, the macro handles recursion through the `typesSeenNow` parameter in `buildTypeExpr`, which tracks types currently being processed to avoid infinite loops. Nested case classes are supported because each level is independently translated into `TypeExpr` structures, allowing the theorem prover to synthesize functions that construct or deconstruct arbitrarily nested products and sums.

### What happens if a sealed trait has non-case-class implementations?

The macro explicitly validates that all direct subclasses of a sealed trait are case classes or case objects using `subclasses.forall { s => resultClass.isCaseClass || resultClass.isModuleClass }`. If this check fails, the macro does not construct a `DisjunctT` and falls back to treating the type as opaque or raises an error, ensuring that only well-formed algebraic data types participate in automatic code generation.

### Does curryhoward support Scala 3's enum types?

The analysis focuses on Scala 2.x macro mechanisms using `scala.reflect` APIs. While Scala 3 enums compile to similar sealed trait hierarchies, the specific reflection logic in [`Macros.scala`](https://github.com/chymyst/curryhoward/blob/main/Macros.scala) targets Scala 2's `isCaseClass` and `isModuleClass` checks. Support for Scala 3 would require adapting the reflection layer to handle the new enum encoding while maintaining the same `TypeExpr` mapping strategy.