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

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**

Re: the usage of val foo = new Foo { val x = 3 }:

I think rewriting object foo extends Foo { val x = 3 } is hard for us Chisel people for multiple reasons:

  • What we actually do is val foo = func(new Foo { val x = 3 }) where func is def func[T](in: T): T. Writing func(object Foo { ... }) is not legal.
  • This pattern is in our hello world. It appears in almost every Chisel user’s code.
    Searching public github repos shows > 4k usages.
  • This is a backwards compatibility problem for all of our users, and it seems onerous to make a code-rewriter and require everyone to use it.
  • More generally, this pattern is useful and important for Chisel’s goals as a hardware DSL. From our experience, structural typing is a very familiar paradigm for Verilog/VHDL users- we try to add more safety while still giving a familiar look and feel.

Is this performance sensitive at all? Accessing those fields uses reflection (and spits out warnings in my IDE and build).

I think we might be able to accommodate your usecase with only minimal changes on your side. In Dotty, structural types need to be converted to Selectable to access the structural members (see Proposal for programmatic structural types for the details), so maybe we could just tweak type inference to make new Foo { val x = 3 } infer Foo { val x: Int } if the expected type is a subtype of Structural, which would be the case if you write def func[T <: Structural](in: T): T.

2 Likes

We control the abstract class that people extend when using this pattern, so if we can just mixin Structural with our abstract class, that would be awesome.

1 Like

@scottcarey it’s not performance sensitive and does use reflection.

@smarter thanks for the link to Selectable- as Jack says, we can control the base type so adding Structural to the mix is doable. From my initial read-through of the proposal, I think it will cover most of our use-cases. We’ll review what’s out in the wild to make sure the limitations in the proposal aren’t a bigger deal than I think they are, but I think they’re unlikely to pop up very often. Thanks!

Ran into the following problem with BracketOps and the implicit def catsEffectSyntaxBracket not being found:

def run[F[_]](implicit F: Bracket[F, Throwable]) = {
  Applicative[F].pure("str").guarantee(finalizer)
}

Implicit needed to be invoked manually for it to compile.

Please provide a full example that can actually be run to reproduce the problem (e.g. with https://scastie.scala-lang.org/)

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