Better type inference for Scala: send us your problematic cases!

Contravariance prevents proper type derivation in Scala 2.13.0; in Dotty it compiles fine. Could this type derivation improvement be back ported?

// if using Scala 2.13.0 a compile error results in marked line below
// ("found Step[B with A]; required Step[A]")
// if using Dotty it compiles fine

// a Step requires some environment
type Step[-E]

// providing a p allows to transform a step that requires an environment with P into a step that does no more require a P
def provide[E, P](p: P): Step[E with P] => Step[E] = ???

trait A
trait B

val step: Step[A with B] = ???

val b: B = ???

// the compile error occurs here
val s: Step[A] = provide(b)(step)

@swachter This should work in 2.13 when using -Xsource:3.0, see https://github.com/scala/scala/pull/6037

Unfortunately Xsource:3.0 did not help (https://scastie.scala-lang.org/swachter/2E1dPtEbS6WDzijFUnUpTQ/1)

Consider reporting this on http://github.com/scala/bug/issues

Thanks for looking into this! Reported Bug: https://github.com/scala/bug/issues/11570

  trait Types {
    type Req
    type Resp
  }

  abstract class Foo[T <: Types](val T: T) {
    def foo(req: T.Req): T.Resp
  }

  abstract class Bar[T <: Types](val T: T) {
    def bar(req: T.Req): T.Resp
  }

  class BarImpl[T <: Types](foo: Foo[T]) extends Bar[T](foo.T) {
    def bar(req: T.Req): T.Resp = foo.foo(req) // doesn't compile
  }
Found:    BarImpl.this.T.Req(req)
Required: BarImpl.this.foo.T'.Req

where:    T  is a value in class Bar
          T' is a value in class Foo

You need to make it:

  class BarImpl[T <: Types](val foo: Foo[T]) extends Bar[foo.T.type](foo.T) {
    def bar(req: T.Req): T.Resp = foo.foo(req) // compiles
  }

or alternatively:

  abstract class Bar[T <: Types] {
    val T: T
    def bar(req: T.Req): T.Resp
  }

  class BarImpl[T <: Types](val foo: Foo[T]) extends Bar[T] {
    val T: foo.T.type = foo.T
    def bar(req: T.Req): T.Resp = foo.foo(req)
  }

You are right that I have to make it val foo: Foo[T]:

  class BarImpl[T <: Types](val foo: Foo[T]) extends Bar(foo.T) {
    def bar(req: T.Req): T.Resp = foo.foo(req)
  }

But still I would like not to deal with this foo.T.type hints

You are not alone:

1 Like

You can force the type parameter of Bar to be a singleton type by using a magic Singleton upper bound:

abstract class Bar[T <: Types & Singleton]

This isn’t ideal though and we’d like to replace it by a more general mechanism (my preference would a precise keyword that can be placed on type parameter and definitions to force type inference to infer the most precise type it finds).

Found a case where Scalac infers stuff fine, but Dotty doesn’t.

Scalac 2.13. Works fine.

Dotty, doesn’t compile.

1 Like

Thanks for the example, could you open an issue for that ?

Also don’t forget to add the “compiles in Scala 2” label!

The following is an issue I stumbled upon in 2.13.1:

trait TestInferrence[T] {

  def getInt(t: T): Int

}

object TestIntInferrence extends TestInferrence[Int] {
  override def getInt(i: Int) = i
}

object InferrenceTest {

  def createNumberHandler[T](
    testInfer: TestInferrence[T] = TestIntInferrence,
    handlers: Map[String, T => Unit] = Map.empty,
  ): T => Unit = {

    (t: T) => {
      testInfer.getInt(t)
      ()
    }

  }

}

class InferrenceTest {

  val handler = InferrenceTest.createNumberHandler()

}

Note if the handlers map argument is removed from createNumberHandler it compiles without problem.

There is a similar ticket for default arg.

A collegue of mine wanted to create a simple example showing the option type.
This is what he wrote:

Some("name").map("Hello " + _)
None.map("Hello " + _)

EDIT(see below):

Some("Hello").map(_ + " world")
None.map(_ + " world")

And it failed, since None has type Option[Nothing].

Obviously, reading it doesn’t make sense (the compiler could even tell: None.map will never be executed, because None is immutable, etc.
In real-life examples, some method will return Option[String] somewhere, and potential Nones would be properly inferred to Option[String]. But since this triggered a discussion about Scala’s type inference, maybe it “reveals” a real underlying issue so I thought it could potentially be helpful to post it here.

Sorry if it’s only a silly use-case.

Actually those two lines just work for me.

Did you mean this perhaps?

None.map(_ + "Hello")

I guess in theory you could make ???.anyNameAtAll(foo, bar) always compile to just ??? since Nothing is a subtype of everything so in theory has every method that any other type in the world potentially has. But I don’t really see what good could come of that.

Did you mean this perhaps?

Sorry, yes. I should have double-checked his example instead of relying on my memory. Apologies.

@david-sledge Looks like your example already works in Dotty.