Description
A few of us discussed this, but it seems we lack an issue for it. Quoting from #4942:
If A <: Singleton and B <: Singleton, it follows that A | B <: Singleton, but for instance 1 | 2 is not actually a singleton type.
In fact, already in Scala 2 we can exploit this and write the questionable
def f[Bound, A <: Bound, B <: Bound](cond: Boolean, a: A, b: B): Bound = if (cond) a else b
def g(cond: Boolean): Singleton = f[Singleton, 1, 2](cond, 1, 2)
In Dotty we also get the clearly broken:
def f[Bound, A <: Bound, B <: Bound](cond: Boolean, a: A, b: B): (A | B) & Bound = if (cond) a else b
def g(cond: Boolean): Singleton = f[Singleton, 1, 2](cond, 1, 2)
// def h(cond: Boolean): (1|2) & Singleton = f[Singleton, 1, 2](cond, 1, 2) // doesn't compile
type || [A, B] = A | B
type Boo = 0 || 1
// def h1(cond: Boolean): (1||2) & Singleton = f[Singleton, 1, 2](cond, 1, 2) // still fails because 1 | 2 is collapsed eagerly
def f2[Bound, A <: Bound, B <: Bound](cond: Boolean, a: A, b: B): (A || B) & Bound = if (cond) a else b
def h2(cond: Boolean): (1||2) & Singleton = f2[Singleton, 1, 2](cond, 1, 2)
def h2(cond: Boolean): Int(1) Any Int(2) & Singleton
Only h
fails, and just because we forbid 1|2
syntactically for now (#1551), so h2
works.
Since Singleton
is special in core typing rules, @odersky suggested this endangers even soundness. Ad-hoc restrictions (say, forbidding Singleton
as a type argument) seems a losing whack-a-mole game.
Discussing this with @odersky today, we figured that Singleton
behaves like a kind that can't be expressed via upper bounds, so we should consider something like an annotation or a modifier (tho modifiers on type variables don't exist yet).
Migration isn't easy, but one can easily support simple use cases of form [X <: Singleton]
or even [X <: Foo with Singleton]
. According to @nicolasstucki, even type SingAlias = Singleton; def foo[X <: Foo with SingAlias]
doesn't currently work.
Paging @milessabin and @soronpo, as this affects SIP-23.