We can look at the initial proposal which added enums to Scala 3 for some insight.
By the wording of this proposal, enum cases fall into three categories.
- Class cases are those cases that are parameterized, either with a type parameter section [...] or with one or more (possibly empty) parameter sections (...).
- Simple cases are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only.
- Value cases are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body.
Let's take a look at your enum declaration.
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
Now, simple cases are right out, because your enum is generic. Your Say is pretty clearly a class case, since it's parameterized. Ask, on the other hand, is non-generic and non-enumerated, so it's a value case. If we scroll down a bit, we see what value cases do
(6) A value case
case C extends <parents> <body>
expands to a value definition
val C = new <parents> { <body>; def enumTag = n; $values.register(this) }
where n is the ordinal number of the case in the companion object, starting from 0. The statement $values.register(this) registers the value as one of the enumValues of the enumeration (see below). $values is a compiler-defined private value in the companion object.
The bottom line here is that Ask is not defined as
object Ask extends Flow[String]
but more like
val Ask = new Flow[String]() { ... }
So your pattern match
case Flow.Ask => StdIn.readLine
is actually an equality check against the value Flow.Ask, rather than a type check against a singleton object. The former, unfortunately, proves nothing to the compiler about the generic type of your value, so it remains T, as opposed to specializing to String.
The rationale for this is provided on the same page
Objectives
...
- Enumerations should be efficient, even if they define many values. In particular, we should avoid defining a new class for every value.
...
So it seems the Scala developers wanted to avoid the bloat that would result from an enum declaring tons of unparameterized values (i.e. a type that looks like an ordinary Java-style enum).
The simple solution, then, is to provide a parameter list
case Ask() extends Flow[String]
It's ever so slightly more cumbersome to work with, but it does define a new type, and then your pattern match
case Flow.Ask() => StdIn.readLine
will succeed