Skip to content

Conversation

esarbe
Copy link

@esarbe esarbe commented Sep 18, 2025

object Animal:
sealed trait Mammal extends Animal
object Mammal:
case object Dog extends Mammal
Copy link
Member

Choose a reason for hiding this comment

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

to avoid getting into the weeds, because this is the wrong desugaring, its probably better to explain it in a set of recursive steps, where each case enum becomes an enum in the companion that extends the parent enum class (because an enum is a class not a sealed trait)
see the spec here: https://scala-lang.org/files/archive/spec/3.4/05-classes-and-objects.html#lowering-of-enum-definitions

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the pointer!

I'll look into that and update the spec.

case enum Mammal:
case Dog, Cat
case enum Bird:
case Sparrow, Pinguin
Copy link
Member

Choose a reason for hiding this comment

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

also good to demonstrate how parameters work (e.g. case enum Bird(val call: String):) and optional extends clauses

Copy link
Member

Choose a reason for hiding this comment

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

also the rules of extends clauses, e.g. what class is allowed to be extended

@bishabosha
Copy link
Member

also another point to address - enum constructor expression are widened to the parent enum, so which parent should a nested case widen to - as a rule it should be included


- `Animal.values` returns all **leaf cases**: `[Dog, Cat, Sparrow, Pinguin, Fish]`
- `Mammal.values` returns `[Dog, Cat]`
- `Mammal.ordinal` and `Mammal.valueOf(...)` are also available
Copy link

@JD557 JD557 Sep 19, 2025

Choose a reason for hiding this comment

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

Maybe I missed it, but I think is not explicit how non-leaf ordinals are computed.

Would it be?

enum Animal:        // No ordinal
  case enum Mammal: // Ordinal 0
    case Dog        // Ordinal 0
    case Cat        // Ordinal 1
  case enum Bird:   // Ordinal 1
    case Sparrow    // Ordinal 2
    case Pinguin    // Ordinal 3
  case Fish         // Ordinal 2

If that is the case, I assume Mammal.fromOrdinal(...) won't exist.
If it does exist, how would Bird.fromOrdinal(...) work? Would it take "global ordinals" (2 for Sparrow and 3 for Penguin), or would it expect "local ordinals" (0 for Sparrow and 1 for Penguin)?

Copy link
Author

Choose a reason for hiding this comment

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

No, you didn't miss it. It's currently not specified.

Do Mammal or Bird, i.e. "sub-enums", need their own ordinal? Can we even instantiate them?

enum Animal:        // No ordinal
  case enum Mammal:       // No ordinal
    case Dog        // Ordinal 0
    case Cat        // Ordinal 1
  case enum Bird:       // No ordinal
    case Sparrow    // Ordinal 2
    case Pinguin    // Ordinal 3
  case Fish         // Ordinal 4

Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

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

One really thorny question is what type gets inferred for term subcases:

val x = Animal.Mammal.Dog

What is the inferred type for x? Choosing Dog.type would be completely inconsistent with current enums. But then is it Animal or Mammal?


The SIP does not mention sub class cases at all. IMO that's missing.

case Sparrow, Pinguin
```

Each nested `enum` case defines a group of related subcases. The **nested enum is itself a valid case** of the parent enum, and its members are **also valid cases**, allowing full exhaustivity and pattern matching.
Copy link
Member

Choose a reason for hiding this comment

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

The nested enum is itself a valid case of the parent enum

This does not make sense to me. It's a subtype, sure. But it cannot be a valid (term) case of the parent enum while at the same time having several "sub-terms". A term must be unique.

The above syntax desugars to an enum tree with subtype relationships:

```scala
sealed trait Animal
Copy link
Member

Choose a reason for hiding this comment

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

Enums are abstract classes, not traits.

Copy link
Author

Choose a reason for hiding this comment

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

fixed.

case Fish => false
```

Matching on a **supercase** (e.g., `Mammal`) is shorthand for matching all its subcases.
Copy link
Member

Choose a reason for hiding this comment

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

This does not make sense. Matching on Mammal itself already has meaning, which is to test if it is the Mammal companion object itself. That cannot be changed to means case Cat | Dog.

Copy link
Author

Choose a reason for hiding this comment

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

True, which is a shame. We can still match on supercase type though.

def laysEggs(a: Animal) = a match {
  case _: Bird | Fish => true
  case _ => false 
}

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.

4 participants