Skip to content
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

Union of literal types is not inferred correctly #22219

Closed
goshacodes opened this issue Dec 16, 2024 · 4 comments · Fixed by #22369
Closed

Union of literal types is not inferred correctly #22219

goshacodes opened this issue Dec 16, 2024 · 4 comments · Fixed by #22369

Comments

@goshacodes
Copy link

goshacodes commented Dec 16, 2024

Compiler version

3.3.4

Minimized code

type MonthNumber =
  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12

List[(String, MonthNumber)](
    "January" -> 1,
    "February" -> 2,
    "March" -> 3,
    "April" -> 4,
    "May" -> 5,
    "June"-> 6,
    "July" -> 7,
    "August" -> 8,
    "September" -> 9,
    "October" -> 10,
    "November" -> 11,
    "December" -> 12
  ).foreach { (name, number) =>
    summon[number.type <:< MonthNumber]
  }

Output

Cannot prove that (number : Int) <:< MonthNumber.
    summon[number.type <:< MonthNumber]

Expectation

Compiles

@goshacodes goshacodes added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 16, 2024
@Gedochao Gedochao added area:typer area:union-types Issues tied to union types. and removed stat:needs triage Every issue needs to have an "area" and "itype" label area:union-types Issues tied to union types. labels Dec 16, 2024
@bracevac
Copy link
Contributor

The problem persists into 3.6.2. The issue appears to specifically affect unions of singleton types from literals + occuring inside a pair type. Other union types will be inferred correctly:

trait Cell[+T]:
  def foreach[U](f: T => U): U

trait A
trait B extends A
trait C extends A
trait D extends A

type Union1 = B | C | D

object X
object Y
object Z

type Union2 = X.type | Y.type | Z.type

type Union3 = "1" | "2" | "3"

trait Test:
  val cell1: Cell[(String,Union1)]
  val cell2: Cell[(String,Union2)]
  val cell3: Cell[(String,Union3)]
  val cell4: Cell[Union3]

  def test =
    cell1.foreach { (s,u) => summon[u.type <:< Union1] }               // ok 
    cell2.foreach { (s,u) => summon[u.type <:< Union2] }               // ok
    cell2.foreach { p     => val u = p._2; summon[u.type <:< Union2] } // ok
    cell3.foreach { (s,u) => summon[u.type <:< Union3] }               // error
    cell3.foreach { p     => val u = p._2; summon[u.type <:< Union3] } // error
    cell4.foreach { u     => summon[u.type <:< Union3] }               // ok (!)

@goshacodes goshacodes changed the title OR type is not inferred correctly Union of literal types is not inferred correctly Dec 16, 2024
@mbovel
Copy link
Member

mbovel commented Jan 12, 2025

This issue was picked for the Scala Issue Spree of tomorrow, Monday, January 13th. @rochala and @iusildra will be working on it. If you have further insight into the issue or guidance on how to fix it, please leave it here.

@rochala
Copy link
Contributor

rochala commented Jan 13, 2025

Spree results:
This is caused by simplification of union types when they are inferred from ref.

List[(String, MonthNumber)](
    "January" -> 1,
    "February" -> 2,
    "March" -> 3,
    "April" -> 4,
    "May" -> 5,
    "June"-> 6,
    "July" -> 7,
    "August" -> 8,
    "September" -> 9,
    "October" -> 10,
    "November" -> 11,
    "December" -> 12
  ).foreach { (name, number) =>
    summon[number.type <:< MonthNumber]
  }

desugars into:

.foreach(x$1: (String, MonthNumber) => {
  val name = x$1._1
  val number = x$1._2
  summon[number.type <:< MonthNumber]
})

This snippet can then be minimized to:

type MonthNumber = 1 | 2
val x: MonthNumber = 1
val y = x

I don't know typer that much, but I suppose this is some kind of simplification during type inference. The place where union type is widened to Int is

TypeOps.simplify(tp.widenTermRefExpr,
if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null)

If someone knows whether this behaviour is intended and can elaborate I'd be glad.

@mbovel
Copy link
Member

mbovel commented Jan 14, 2025

The culprit is widenSingletons called from widenInferred. I am wondering if it shouldn't preserve hard unions. It was last changed in #19995.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants