Skip to content

Allow Singleton types in Union types #1551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
odersky opened this issue Oct 2, 2016 · 6 comments
Closed

Allow Singleton types in Union types #1551

odersky opened this issue Oct 2, 2016 · 6 comments

Comments

@odersky
Copy link
Contributor

odersky commented Oct 2, 2016

#1550 disallows singleton types in union types to avoid the problems uncovered by #829. But it would be good to allow them, if we can find a solid solution.

The current scheme is: (1) singleton types in unions are not allowed. (2) Therefore, when taking the lub, we widen every singleton type. Thats simple and consistent, but also restrictive.

@smarter
Copy link
Member

smarter commented Oct 11, 2016

Note that singleton types in unions can still slip through the cracks:

scala> def or[T](x: T | Int) = x 
or: [T](x: T | Int)T | Int
scala or["foo"]("bar") 
res13: String | Int = bar

It's probably not worth trying to disallow this case before we try to make singleton types in unions actually work.

@propensive
Copy link
Contributor

propensive commented May 18, 2017

I uncovered another valid -- I think -- use-case while experimentitng with enums, which doesn't involve singleton literal types.

This is intended to be a linked-list-like datastructure, but with alternating types, and it might make a good test case, but currently doesn't compile due to the restriction.

enum Fence[+T, +S] {
  case End
  case Post(value: T, next: Panel[T, S] | End.type)
  case Panel(value: S, next: Post[T, S])
}

import Fence._
val fence = Post(1, Panel("two", Post(3, End)))

@soronpo
Copy link
Contributor

soronpo commented Feb 6, 2018

Would have been nice to do:
type Binary = 0 | 1
There are other ways to achieve this constraint, though.

@panacekcz
Copy link
Contributor

Just a curiosity, the or["foo"]("bar") example doesn't compile anymore, but it is still possible to get unsoundness with literal types in a union.

object Get2As1 {
  class OrIntroFn[T, U, TU >: T|U]{
    type V = TU
    def tuToV(): (TU => V) = p => p
  }
  class Or11X[X] extends OrIntroFn[1&1, X, (1&1|X)]{
    def get2as11X:V = tuToV()(2)
  }
  class Or11Nothing extends Or11X[Nothing]
  def get2as1:1 = new Or11Nothing().get2as11X

  def main(a:Array[String]) = {
    println(get2as1) // prints 2
    val one:1 = get2as1
    println(one) // prints 1
  }
}

@godenji
Copy link

godenji commented Dec 31, 2018

In TypeScript this isn't an issue:

type ABC = 'A' | 'B' | 'C'
type A2F = ABC | 'D' | 'E' | 'F'

foo(x: A2F) {...}
foo('F') // ok
foo('G') // fails

Union types combined with string literals are quite powerful, not to mention concise -- above approach is used all the time in TypeScript -- would be great if this restriction were lifted.

We can perhaps workaround the issue with enums, but that's cumbersome in comparison to union types + string literals, which, if supported, wouldn't necessarily require defining a separate type; i.e. could just define in-place:

def foo(x: "A" | "B" | "C") = ...

useful for one-off cases where you want type safety but not be bothered with introducing a separate type.

@smarter
Copy link
Member

smarter commented May 2, 2019

Fixed by #6299

@smarter smarter closed this as completed May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants