Search code examples
type-inferencescala-3

scala : Type inference on a class member


Consider the following code : scastie

class A           {              }
class B extends A { def m = true }

trait X           { def obj : A     }
class Y extends X { def obj = new B }

val y = new Y
y.obj.m // Compiles in scala2, does not compile in scala3.

What is the type of y.obj ?

It appears that in scala2 y.obj is a B, while in scala3 y.obj is a A ! As a result looking up for y.obj.m compiles in scala2, but not in scala3.

Apparently, in scala3, the type of the abstract member def obj: A in X takes precedence over the narrower type inference in Y.

As expected, y.obj is a B when :

class Y /* extends X */ { def obj     = new B } // member is not previously declared
class Y    extends X    { def obj : B = new B } // type is forced by ascription

This looks benign, until one uses transparent inline methods to ship back some type information out of a macro.

  • Is the discrepancy between scala2 and scala3 expected ?
  • What is the intention ?
  • Is there a workaround ?

Here is a more descriptive snippet: scastie

class A
class B extends A:
  def m = true

transparent inline def choose(b: Boolean): A =
  if b then new A else new B


class X:
  def obj1 : A
  def obj2 : A
  def obj3 : A

class Y extends X:
  def obj1     = choose(true)  // type A
  def obj2     = choose(false) // type A ( from X.this.obj2 )
  def obj3 : B = choose(false) // type B ( from ascription  )
  def obj4     = choose(false) // type B ( from inference   )

val y = new Y
y.obj1.m // compile error     EXPECTED
y.obj2.m // compile error   UNEXPECTED
y.obj3.m // compiles          EXPECTED
y.obj4.m // compiles          EXPECTED

Solution

  • Is the discrepancy between scala2 and scala3 expected ?

    Yes. Here is the intention.

    Is there a workaround ?

    • Yes. Declaring the member as final prevents the widening.
    • However, final val is expected to be phased out from scala3, superseded by inline val.
    • inline def compiles correctly, but also inlines the definition-rhs at the call site, which may not be the intention.
    • inline val is restricted to literal constants.

    A SIP about a precise modifier is in discussion here.