question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Type inference difficulties with `UndefOr[A | B]`

See original GitHub issue

Compiler version

3.1.3-RC1-bin-20220323-fb7f900-NIGHTLY

Minimized code

UndefOr is based on the Scala.js concept:

https://github.com/scala-js/scala-js/blob/058532aa8c504b76431b40e3e1b51b2cdef87643/library/src/main/scala/scala/scalajs/js/package.scala#L85

The minimized example below demonstrates a common situation when working with Scala.js facades.

//> using scala "3.1.3-RC1-bin-20220323-fb7f900-NIGHTLY"

type UndefOr[A] = A | Unit

extension [A](maybe: UndefOr[A])
  def foreach(f: A => Unit): Unit =
    maybe match
      case () => ()
      case a: A => f(a)

trait Foo
trait Bar

object Baz:
  var booBap: Foo | Bar = _

def z: UndefOr[Foo | Bar] = ???

@main
def main =
  z.foreach(x => Baz.booBap = x)

Output

$ scala-cli compile test.scala 
Compiling project (Scala 3.1.3-RC1-bin-20220323-fb7f900-NIGHTLY, JVM)
[error] ./test.scala:21:31: Found:    (x : Object)
[error] Required: Foo | Bar
[error]   z.foreach(x => Baz.booBap = x)
[error]                               ^
Error compiling project (Scala 3.1.3-RC1-bin-20220323-fb7f900-NIGHTLY, JVM)
Compilation failed

Expectation

If we add an explicit type annotation to the last line:

- z.foreach(x => Baz.booBap = x)
+ z.foreach((x: Foo | Bar) => Baz.booBap = x)

then it compiles ok:

$ scala-cli compile test.scala 
Compiling project (Scala 3.1.3-RC1-bin-20220323-fb7f900-NIGHTLY, JVM)
[warn] ./test.scala:9:12: the type test for A cannot be checked at runtime
[warn]       case a: A => f(a)
[warn]            ^
Compiled project (Scala 3.1.3-RC1-bin-20220323-fb7f900-NIGHTLY, JVM)

Is it possible for the compiler to make this inference by itself?

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:7 (6 by maintainers)

github_iconTop GitHub Comments

4reactions
smartercommented, May 9, 2022

I don’t think it’s by design, we have some logic to try to preserve hard unions (that is: unions written by the user rather than inferred) through type inference already: https://github.com/lampepfl/dotty/blob/6540ad9154797164561281080b6a4e24a191691d/compiler/src/dotty/tools/dotc/core/TypeComparer.scala#L470-L478 But this is fragile: it only kicks in when the rhs of the type comparison is a type variable or an intersection type, whereas here the rhs is a union type (but one side of the union is also part of the lhs union, and the other is a type variable). More generally, I think this code is problematic since it means we record unions in lower bounds of type variables which violates the precondition of addConstraint: https://github.com/lampepfl/dotty/blob/6540ad9154797164561281080b6a4e24a191691d/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala#L651-L658 A possible alternative would be to add a Mode flag (or boolean var) we would set in TypeComparer to tell the ConstraintHandling logic to create hard unions instead of soft unions when refining a lower bound: https://github.com/lampepfl/dotty/blob/6540ad9154797164561281080b6a4e24a191691d/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala#L266 (Type#| creates soft unions by default)

(This might also help with other issues where we currently don’t preserve unions: https://github.com/lampepfl/dotty/issues/14494) /cc @mbovel

1reaction
rmgkcommented, Mar 24, 2022

Basically it does not seem to infer sub parts of a union. Smaller example:

scala> def test[A](v: A | Unit): A | Unit =  v
 
scala> test(5: Int | Unit)
val res7: Int | Unit = 5

scala> test(5: String | Int | Unit)
val res8: Matchable | Unit = 5
Read more comments on GitHub >

github_iconTop Results From Across the Web

Type inference has usability problems
I argue that it may hinder comprehension and increase cognitive load. To see the discussion about this article, see the post on Hacker...
Read more >
Better implicits for combinations of js.| and js.UndefOr? #2067
@sjrd is there are way to make Union types working with js.UndefOr types? Since both are implicits and scala only uses one conversion...
Read more >
Working with objects
there are various setters for b , c and d . This is primarily because of type inference. For instance if setC was...
Read more >
Type Inference | Tour of Scala
The compiler uses the types of the arguments of MyPair to figure out what type A and B are. Likewise for the type...
Read more >
Inference | Classroom Strategies
This helps students understand the different types of information they use to ... as well as the steps to solving a math problem...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found