Skip to content

Commit aa2f907

Browse files
committed
Drop special case around Function1
Now only Scala2 mode treats Function1's as implicit conversions. Instead we introduce a new subclass ImplicitConverter of Function1, instances of which are turned into implicit conversions.
1 parent 180dfdc commit aa2f907

File tree

8 files changed

+40
-26
lines changed

8 files changed

+40
-26
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

+2
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ class Definitions {
338338
def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol
339339

340340
def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny)
341+
lazy val Predef_ImplicitConverterR = DottyPredefModule.requiredClass("ImplicitConverter").typeRef
342+
def Predef_ImplicitConverter(implicit ctx: Context) = Predef_ImplicitConverterR.symbol
341343

342344
lazy val DottyArraysModuleRef = ctx.requiredModuleRef("dotty.runtime.Arrays")
343345
def DottyArraysModule(implicit ctx: Context) = DottyArraysModuleRef.symbol

compiler/src/dotty/tools/dotc/typer/Implicits.scala

+10-20
Original file line numberDiff line numberDiff line change
@@ -86,35 +86,25 @@ object Implicits {
8686
// However, Predef.$conforms is not eligible, because it is a no-op.
8787
//
8888
// In principle, it would be cleanest if only implicit methods qualified
89-
// as implicit conversions. The reasons for deviating are as follows:
90-
// Keeping Function1: It's still used quite often (for instance, view
91-
// bounds map to implicits of function types) and there is no feasible workaround.
92-
// One tempting workaround would be to add a global def
93-
//
94-
// implicit def convertIfFuntion1[A, B](x: A)(implicit ev: A => B): B = ev(a)
95-
//
96-
// But that would throw out the baby with the bathwater. Now, every subtype of
97-
// function gives again rise to an implicit conversion. So it's better to just accept
98-
// function types in their dual roles.
99-
//
100-
// The reason for the treatment of <:< and conforms is similar. We could
101-
// avoid the clause by having a standard conversion like this in Predef:
89+
// as implicit conversions. We could achieve that by having standard conversions like
90+
// this in Predef:
10291
//
10392
// implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a)
93+
// implicit def convertIfConverter[A, B](x: A)(implicit ev: ImplicitConverter[A, B]): B = ev(a)
10494
//
105-
// But that would slow down implicit search a lot, because this conversion is
106-
// eligible for all pairs of types, and therefore is tried a lot. So we
107-
// emulate the existence of a such a conversion directly in the search.
95+
// (Once `<:<` inherits from `ImplicitConverter` we only need the 2nd one.)
96+
// But clauses like this currently slow down implicit search a lot, because
97+
// they are eligible for all pairs of types, and therefore are tried too often.
98+
// We emulate instead these conversions directly in the search.
10899
// The reason for leaving out `Predef_conforms` is that we know it adds
109100
// nothing since it only relates subtype with supertype.
110101
//
111102
// We keep the old behavior under -language:Scala2.
112-
val isFunction =
113-
if (ctx.scala2Mode) tpw.derivesFrom(defn.FunctionClass(1))
114-
else tpw.isRef(defn.FunctionClass(1))
103+
val isFunctionInS2 = ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1))
104+
val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter)
115105
val isConforms =
116106
tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms
117-
!(isFunction || isConforms)
107+
!(isFunctionInS2 || isImplicitConverter || isConforms)
118108
}
119109

120110
def discardForValueType(tpw: Type): Boolean = tpw match {

library/src/dotty/DottyPredef.scala

+20
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,24 @@ object DottyPredef {
3636
implicit def eqNumFloat : Eq[Number, Float] = Eq
3737
implicit def eqDoubleNum: Eq[Double, Number] = Eq
3838
implicit def eqNumDouble: Eq[Number, Double] = Eq
39+
40+
/** A class for implicit values that can serve as implicit conversions
41+
* The implicit resolution algorithm will act as if there existed
42+
* the additional implicit definition:
43+
*
44+
* def $implicitConversion[T, U](x: T)(c: ImplicitConverter[T, U]): U = c(x)
45+
*
46+
* However, the presence of this definition would slow down implicit search since
47+
* its outermost type matches any pair of types. Therefore, implicit search
48+
* contains a special case in `Implicits#discardForView` which emulates the
49+
* conversion in a more efficient way.
50+
*
51+
* Note that this is a SAM class - function literals are automatically converted
52+
* to `ImplicitConverter` values.
53+
*
54+
* Also note that in bootstrapped dotty, `Predef.<:<` should inherit from
55+
* `ImplicitConverter`. This would cut the number of special cases in
56+
* `discardForView` from two to one.
57+
*/
58+
abstract class ImplicitConverter[-T, +U] extends Function1[T, U]
3959
}
File renamed without changes.
File renamed without changes.

tests/pos/t0786.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object ImplicitProblem {
1515
def eval = f(nullval[T]).eval + 1
1616
}
1717

18-
def depth[T](n: T)(implicit ev: T => Rep[T]) = n.eval
18+
def depth[T](n: T)(implicit ev: T => Rep[T]) = ev(n).eval
1919

2020
def main(args: Array[String]): Unit = {
2121
println(depth(nullval[M[Int]])) // (1) this works

tests/run/iterator-from.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ object Test extends dotty.runtime.LegacyApp {
1111
val maxKey = 50
1212
val maxValue = 50
1313

14-
def testSet[A <% Ordered[A]](set: SortedSet[A], list: List[A]): Unit = {
14+
implicit def convertIfView[A](x: A)(implicit view: A => Ordered[A]): Ordered[A] = view(x)
15+
16+
def testSet[A: Ordering](set: SortedSet[A], list: List[A]): Unit = {
1517
val distinctSorted = list.distinct.sorted
1618
assertEquals("Set size wasn't the same as list sze", set.size, distinctSorted.size)
1719

@@ -24,7 +26,7 @@ object Test extends dotty.runtime.LegacyApp {
2426
}
2527
}
2628

27-
def testMap[A <% Ordered[A], B](map: SortedMap[A, B], list: List[(A, B)]): Unit = {
29+
def testMap[A: Ordering, B](map: SortedMap[A, B], list: List[(A, B)]): Unit = {
2830
val distinctSorted = distinctByKey(list).sortBy(_._1)
2931
assertEquals("Map size wasn't the same as list sze", map.size, distinctSorted.size)
3032

tests/run/t8280.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@ object Moop3 {
7474
// Dotty deviation. This fails for Dotty with ambiguity error for similar reasons as ob1.
7575
}
7676
object ob2 {
77-
implicit val f1: Int => String = _ => "Int"
77+
implicit val f1: ImplicitConverter[Int, String] = _ => "Int"
7878
implicit def f2(x: Long): String = "Long"
7979

8080
println(5: String)
8181
}
8282
object ob3 {
83-
implicit val f1: Int => String = _ => "Int"
84-
implicit val f2: Long => String = _ => "Long"
83+
implicit val f1: ImplicitConverter[Int, String] = _ => "Int"
84+
implicit val f2: ImplicitConverter[Long, String] = _ => "Long"
8585

8686
println(5: String)
8787
}

0 commit comments

Comments
 (0)