Skip to content

Take expected result type into account for overloading resolution #665

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

Merged

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jun 18, 2015

Previously, the expected result type of a FunProto type was ignored and taken into
account only in case of ambiguities. run/arrayclone-new.scala shows that this is not enough.
In a case like

val x: Array[Byte] = Array(1, 2)

we typed 1, 2 to be Int, so overloading resolution would give the Array.apply of
type (Int, Int*)Array[Int]. But that's a dead end, since Array[Int] is not a subtype
of Array[Byte].

This commit proposes the following modified rule for overloading resolution:

A method alternative is applicable if ... (as before), and if its result type
is compatible with the expected type of the method application.

The commit does not pre-select alternatives based on comparing with the expected
result type. I tried that but it slowed down typechecking by a factor of at least 4.
Instead, we proceed as usual, ignoring the result type except in case of
ambiguities, but check whether the result of overloading resolution has a
compatible result type. If that's not the case, we filter all alternatives
for result type compatibility and try again.

Review by @adriaanm

@odersky odersky force-pushed the fix/#647-expected-type-overloading branch from 3eda187 to 7f2c2b9 Compare June 18, 2015 08:52
@odersky
Copy link
Contributor Author

odersky commented Jun 18, 2015

There's a download failure for jline 2.12.jar. What to do?

@DarkDimius
Copy link
Contributor

/rebuild

@@ -1241,7 +1241,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def expectedStr = err.expectedTypeStr(pt)
resolveOverloaded(alts, pt) match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether resolveOverloaded could do adaptByResult's work from the start? What's the rationale for doing this afterwards?

@odersky
Copy link
Contributor Author

odersky commented Jun 18, 2015

@adriaanm It would be much slower. I tried that first and it looked like at least 4 times blow-up in compilation times.I was surprised at that drastic result, and don't really have an explanation for the magnitude of it. But in principle it makes sense: When doing overloading resolution, we want to prune the search space as quickly as possible. Results of overloaded methods tend to be similar, so checking them first does not prune much. Better to concentrate on the arguments first.

@adriaanm
Copy link
Contributor

Ok, would be good to include this as a comment to avoid someone slowing down the compiler if they happened to implement what I was wondering about :-)

@adriaanm
Copy link
Contributor

One last concern: are there other calls to resolveOverloaded that need similar post-processing? If so, it would still make sense to create a separate method that combines resolveOverloaded and adaptByResult.

@retronym
Copy link
Member

What alternative of Array.apply is selected after this change? In Scalac, we fall back to the generic version ([T](xs: T*)(implicit evidence$2: scala.reflect.ClassTag[T])Array[T]).

% cat sandbox/test.scala
class Test {
  val x: Array[Byte] = Array(1, 2)
}
% qscalac -Xprint:typer sandbox/test.scala
[[syntax trees at end of                     typer]] // test.scala
package <empty> {
  class Test extends scala.AnyRef {
    def <init>(): Test = {
      Test.super.<init>();
      ()
    };
    private[this] val x: Array[Byte] = scala.Array.apply[Byte](1, 2)((ClassTag.Byte: scala.reflect.ClassTag[Byte]));
    <stable> <accessor> def x: Array[Byte] = Test.this.x
  }
}

This comes from the the way that inferMethodAlternative tries to resolve a winner 1) with and then 2) without the expected type.

@retronym
Copy link
Member

See also https://issues.scala-lang.org/browse/SI-8230, in which I've extract a standalone test case to mimic the overloads and associated implicit conversions of Array#apply, which leads to this puzzler:

trait Arr[T]
object Arr {
  def apply[T](xs: T): Arr[T]    = null
  def apply(x: Long) : Arr[Long] = null
}

object I {
  implicit def arrToTrav[T] (a: Arr[T])   : Traversable[T]    = null
  implicit def longArrToTrav(a: Arr[Long]): Traversable[Long] = null
}

object Test {
  def foo(t: Traversable[Any]) {}

  object Okay {
    Arr("1")

    import I.{ arrToTrav, longArrToTrav }
    foo(Arr("2"))
  }

  object Fail {
    import I.arrToTrav
    foo(Arr("3")) // found String, expected Long
  }
}

I traced out the type inference here: scala/scala@2.11.x...retronym:ticket/8230

@retronym
Copy link
Member

Overall, I think any intentional divergence between the Scala and dotty type checker around overloading/inference needs to be strongly motivated and thoroughly tested. While it can be tricky to reverse engineer some of the workings of the current system, it does represent "the devil we know".

odersky added 2 commits June 19, 2015 12:58
…olution

Previously, the expected result type of a FunProto type was ignored and taken into
account only in case of ambiguities. arrayclone-new.scala shows that this is not enough.
In a case like

    val x: Array[Byte] = Array(1, 2)

we typed 1, 2 to be Int, so overloading resulution would give the Array.apply of
type (Int, Int*)Array[Int]. But that's a dead end, since Array[Int] is not a subtype
of Array[Byte].

This commit proposes the following modified rule for overloading resulution:

  A method alternative is applicable if ... (as before), and if its result type
  is copmpatible with the expected type of the method application.

The commit does not pre-select alternatives based on comparing with the expected
result type. I tried that but it slowed down typechecking by a factor of at least 4.
Instead, we proceed as usual, ignoring the result type except in case of
ambiguities, but check whether the result of overloading resolution has a
compatible result type. If that's not the case, we filter all alternatives
for result type compatibility and try again.
Previously, this was not done when resolving through tpd.
Also, improve comments to explain why we picked the "check
afterwards" strategy.

Finally, refactor so that the new logic is all inside
resolveOverloaded.
@odersky odersky force-pushed the fix/#647-expected-type-overloading branch from 7f2c2b9 to aef3d57 Compare June 19, 2015 11:20
New test case: array-overload.scala. Failed before, succeeds now.
@odersky
Copy link
Contributor Author

odersky commented Jun 19, 2015

@retronym I verified that dottys resolution is the same as scalac's. t8230a.scala is a crasher after the typer. See #670.

odersky added a commit that referenced this pull request Jun 19, 2015
…rloading

Take expected result type into account for overloading resolution
@odersky odersky merged commit c54debe into scala:master Jun 19, 2015
@allanrenucci allanrenucci deleted the fix/#647-expected-type-overloading branch December 14, 2017 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants