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

@LPTK I think this is equivalent to:

(x => x)(1)

All I can say without looking deeper into it is that it doesn’t seem impossible, maybe open an issue so we keep track of that ?

@lrytz PRs adding test cases are more than welcome!

@smarter no it’s not. The following works well in all versions of Scala:

implicit class Helper[A](self: A) {
  def |> [B] (rhs: A => B): B = rhs(self)
}
1 |> (x => x)

Ah indeed, can you rewrite it without the cats dependency to make things simpler to analyze ?

Here’s a bizarre implicit resolution problem that I once found:

trait Typeclass[T]
object Typeclass {
  implicit def seqTC[C[X] <: Seq[X], T: Typeclass]: Typeclass[C[T]] = ???
  implicit def setTC[C[X] <: Set[X], T: Typeclass]: Typeclass[C[T]] = ???
}

class Stuff[T]
object Stuff {
  implicit val stuffTC: Typeclass[Stuff[_]] = ???
}

object Test {
  implicitly[Typeclass[Stuff[_]]] // error!
}

The result is:

error: diverging implicit expansion for type Typeclass[Stuff[_]]
starting with method setTC in object Typeclass
         implicitly[Typeclass[Stuff[_]]]

Workaround for this is to add redundant with Seq/Set[T] to the result types of implicits:

implicit def seqTC[C[X] <: Seq[X], T: Typeclass]: Typeclass[C[T] with Seq[T]] = ???
implicit def setTC[C[X] <: Set[X], T: Typeclass]: Typeclass[C[T] with Set[T]] = ???

@ghik Already works in Dotty!

2 Likes

I maintain a project (https://chisel.eecs.berkeley.edu/) where our users commonly use anonymous classes as in the following pattern:

Example 1

Implementing trait field with anonymous subclass:

abstract class Foo
trait Bar {
  def foo: Foo
}
object Test extends App with Bar {
  val foo = new Foo {
    val x = 3
  }
  println(foo.x)
}

This is my main use case but there is a similar one:

Example 2

Merely having a field that is an anonymous subclass:

abstract class Foo
object Test extends App {
  val foo = new Foo {
    val x = 3
  }
  println(foo.x)
}

The above examples can also be found on my Github repo: https://github.com/jackkoenig/scala-anon-class-type-inference

Those changes are intentional:

  1. When a definition is overridden, if no explicit result type is given, we now always pick the result type of the overridden definition instead of inferring a more specific type (I’m actually not sure if this is the exact behavior Scala 2.12 follows but it is Dotty’s behavior). This avoids accidentally changing your API when you change the bodies of definitions.
  2. When the result type of an anonymous class is inferred, Dotty never infers a structural type with extra definitions (i.e. Foo { val x: Int } in your example), these types are problematic since calling x requires using Java runtime reflection.

It’d be useful to know more about your usecases to think of alternative patterns that could be used for them.

It looks similar to issue#9487

When a type of an anonymous class is less specific that the definition of the class (AnyRef for example) the anonymous class methods become private.

Like @jackkoenig above, I’m involved in chisel. In attempting to see how much work porting the firrtl sub-project will be, I ran into an issue. It boils down to this:

trait A {
  val a: Int = 0
}

trait B {
  val b: Int = 1
}

object C {
  def apply(b: B): Unit = b match {
    case a: A => println(s"${a.a + a.b}")
  }
}

This example typechecks in scala 2.12, but not dotty 0.13.0-RC1. Is this intended behavior that is considered good? The advice the compiler gives is ${a.a + b.b}, which seems bad because it hides the fact that a and b are the same. I also tried case a: A | B => which I thought should work but is evidently wrong.

That one is an actual issue you can work around by doing case a: A & B => ..., see https://github.com/lampepfl/dotty/issues/3208, if this is blocking you from porting code we can try to prioritize it.

Good to know, thanks for the issue link. Rather than blocking, I’d say slowing- there are lots of instances of this issue.

Can this restriction be lifted for final val overrides (which are supposed to always narrow, under the Literal Singleton Types proposal, afaik)? I have multiple pieces of code that intentionally use override narrowing to either 1. compute String singletons 2. derive & recover type members from overrides of GADT member values, e.g.

private[example] trait HandlerBase {
  type Target
  def handlesTarget: TargetGroup.Aux[Target]
  protected val target: TargetGroup

  def process(t: Target): Unit
}
// derive type Target from `final val target`
trait Handler extends HandlerBase {
  override type Target = target.T
  override def handlesTarget: TargetGroup.Aux[target.T] = target
}

sealed trait TargetGroup { type T }
case object strTgt Textends TargetGroup { type T = String }

// usage
final class Handler1 extends Handler {
  final val target = strTgt
  def process(t: String): Unit = {}
}

That sounds reasonable and in fact it’s already how Dotty behaves!

A way to maintain the old meaning of

would be to rewrite it to:

object foo extends Foo { val x = 3 }

That would keep x as a visible member of foo.

One caveat here:

object foo extends Foo { val x = 3 }
// is eq to
lazy val foo  = new Foo { val x = 3 }
//------------------------------------

object foo extends Foo { val x = 3 }
foo //this line produces warning ...
// is eq to
val foo  = new Foo { val x = 3 }

if(true){
  true: java.lang.Boolean
}else{
  false
}

Scala 2.x can’t infer the correct type of this expression, and many other expressions where one branch is a subclass of AnyVal and the other is the java wrapper. Not sure how easy this would be to fix, since it has to do with implicit conversions.

This came up for real when I had to interact with Java I was doing something like this

public class User {
    private Boolean isEnabled;
    //getters setters, etc...
}

And in scala I was doing something like

val maybeNullPerson = Option(getUser())
val thatPersonEnabled = maybeNullPerson.map(_.isEnabled).getOrElse(false)
if(thatPersonEnabled) {
//...

Since the inferred type was Any I had to do this

val thatPersonEnabled = maybeNullPerson.map(_.isEnabled).getOrElse[java.lang.Boolean](false)

I’m not convinced Scala should infer anything other than Any here. Which type would you consider “correct”?

Yeah, the current behavior might be the best, but the other option would be to automatically infer java.lang.Boolean, since that’s the wrapper type for scala.Boolean.

Why not scala.Boolean in this case? This value is used in if in your example. So, it’s unclear why complier should prefer Java’s one.

Why you think compiler should apply implicit conversion inside if and at other usages of thatPersonEnabled as boolean (inferring java.lang.Boolean for thatPersonEnabled) instead of applying implicit conversion at _.isEnabled call (thus inferring scala.Boolean for thatPersonEnabled).

java.lang.Boolean can be null, but scala.Boolean cannot, so I would not want to convert.

On the other hand, Scala thinks that’s fine:

**Welcome to Scala 2.12.7 (OpenJDK 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.

(true: java.lang.Boolean).asInstanceOf[Boolean]
res0: Boolean = true
(null: java.lang.Boolean).asInstanceOf[Boolean]
res1: Boolean = false**