Skip to content

Unexpected type widening during inference (behaves differently in Scala 2) #14930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
kitlangton opened this issue Apr 13, 2022 · 2 comments
Open
Labels

Comments

@kitlangton
Copy link

kitlangton commented Apr 13, 2022

Compiler version

3.1.1

Minimized code

Here's a Scastie with the unexpected behavior in Scala 3. And here's a Scastie with the expected behavior in Scala 2.

Here's the gist:

// Given a constructor that infers types from given a function
def fromFunction[A, B, C](f: (A, B) => C)(implicit tagA: Tag[A], tagB: Tag[B]): Box[A with B] =
  Box(Map("a" -> tagA, "b" -> tagB))
 
// It behaves differently depending on whether or not the call site is ascribed a type

// No type ascription
val box1 = fromFunction((int: Int, string: String) => true)
println(box2) // Infers A and B correctly

// With a type ascription
val box2: Box[Int with String] = fromFunction((int: Int, string: String) => true)
println(box3) // Infers A and B to both be (A & B)

Output

 // In the case without the type ascription
Box(Map(a -> Tag[Int], b -> Tag[String]))

// In the case with the type ascription
Box(Map(a -> Tag[{String & Int}], b -> Tag[{String & Int}])) 

Expectation

I would expect both cases to infer with a bias toward the input type, which is function of type (Int, String) => Boolean. So I would expect both cases to print Box(Map(a -> Tag[Int], b -> Tag[String])), as it does in the linked Scala 2 version.

Concretely, this is used in ZIO to generate "Layers" from functions. For now, in Scala 3, it is necessary to either remove the type ascription or create an intermediate val without an ascription.

@kitlangton kitlangton added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 13, 2022
@romanowski romanowski added area:typer and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 13, 2022
@smarter
Copy link
Member

smarter commented Apr 13, 2022

Minimized to not depend on external code:

class Tag[A]
class Box[-A]

class A {
  implicit val tagInt: Tag[Int] = ???
  implicit val tagString: Tag[String] = ???

  def fromFunction[A, B, C](f: (A, B) => C)(implicit tagA: Tag[A], tagB: Tag[B]): Box[A with B] = ???
  
  val box2 =
    fromFunction((int: Int, string: String) => true) // OK: Infers ?A := Int, ?B = String
  val box3: Box[Int with String] =
    fromFunction((int: Int, string: String) => true) // error: Infers ?A := Int & String, ?B = Int & String
}

When we get to the implicit parameter block we have as constraints:

     ?A >: Int & String <: Int
     ?B >: Int & String <: String

(the lower bound constraints come from the expected type, the upper bound constraints come from the lambda parameter types).

The logic used to instantiate type variables before implicit search is:
https://github.com/lampepfl/dotty/blob/f4822ac22bd7bc8eecb134ca5dc39585be9ff357/compiler/src/dotty/tools/dotc/typer/Inferencing.scala#L56-L65
Here, 1. applies: ?A is constrained from below to Int & String so we instantiate it to Int & String, same for ?B. The heuristic is that doing an implicit search with more precisely constrained types is more likely to yield the result you're looking for. It turns out that in this particular case this isn't true, but it's not clear to me if there's anything we can do to handle this case differently without breaking other things.

In conclusion, I don't recommend relying on type inference implementation details, especially when intersection/union types are involved 😅

@smarter smarter added the area:implicits related to implicits label Apr 13, 2022
@smarter
Copy link
Member

smarter commented Apr 13, 2022

For reference, this isn't the first time that we have inference issues involving intersections in the expected type, I was able to significantly improve the situation in #8635, the key difference here is the interaction between type inference and implicit search which is poorly understood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants