Skip to content

Commit bf9bdae

Browse files
authored
Merge pull request #2065 from dotty-staging/change-implicit-conv2
Disallow subtypes of Function1 acting as implicit conversions
2 parents 391aaa1 + aa2f907 commit bf9bdae

File tree

12 files changed

+81
-14
lines changed

12 files changed

+81
-14
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ class Definitions {
299299
lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef")
300300
def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol
301301

302+
lazy val Predef_ConformsR = ScalaPredefModule.requiredClass("$less$colon$less").typeRef
303+
def Predef_Conforms(implicit ctx: Context) = Predef_ConformsR.symbol
302304
lazy val Predef_conformsR = ScalaPredefModule.requiredMethodRef("$conforms")
303305
def Predef_conforms(implicit ctx: Context) = Predef_conformsR.symbol
304306
lazy val Predef_classOfR = ScalaPredefModule.requiredMethodRef("classOf")
@@ -336,6 +338,8 @@ class Definitions {
336338
def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol
337339

338340
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
339343

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

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,29 @@ object Implicits {
8282
case tpw: TermRef =>
8383
false // can't discard overloaded refs
8484
case tpw =>
85-
//if (ctx.typer.isApplicable(tp, argType :: Nil, resultType))
86-
// println(i"??? $tp is applicable to $this / typeSymbol = ${tpw.typeSymbol}")
87-
!tpw.derivesFrom(defn.FunctionClass(1)) ||
88-
ref.symbol == defn.Predef_conforms //
89-
// as an implicit conversion, Predef.$conforms is a no-op, so exclude it
85+
// Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views.
86+
// However, Predef.$conforms is not eligible, because it is a no-op.
87+
//
88+
// In principle, it would be cleanest if only implicit methods qualified
89+
// as implicit conversions. We could achieve that by having standard conversions like
90+
// this in Predef:
91+
//
92+
// 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)
94+
//
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.
99+
// The reason for leaving out `Predef_conforms` is that we know it adds
100+
// nothing since it only relates subtype with supertype.
101+
//
102+
// We keep the old behavior under -language:Scala2.
103+
val isFunctionInS2 = ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1))
104+
val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter)
105+
val isConforms =
106+
tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms
107+
!(isFunctionInS2 || isImplicitConverter || isConforms)
90108
}
91109

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

library/src/dotty/DottyPredef.scala

Lines changed: 20 additions & 0 deletions
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
}

tests/neg/falseView.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test {
2+
3+
private implicit val xs: Map[String, Int] = ???
4+
5+
val x: Int = "abc" // error
6+
7+
}
File renamed without changes.

tests/pos/t0786.scala

Lines changed: 1 addition & 1 deletion
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/pos/t2421_delitedsl.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
trait DeliteDSL {
22
abstract class <~<[-From, +To] extends (From => To)
3+
34
implicit def trivial[A]: A <~< A = new (A <~< A) {def apply(x: A) = x}
5+
implicit def convert_<-<[A, B](x: A)(implicit ev: A <~< B): B = ev(x)
6+
47

58
trait Forcible[T]
69
object Forcible {

tests/run/iterator-from.scala

Lines changed: 4 additions & 2 deletions
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/puzzler54.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Scala Puzzler 54
2+
object Test {
3+
case class Card(number: Int, suit: String = "clubs") {
4+
val value = (number % 13) + 1 // ace = 1, king = 13
5+
def isInDeck(implicit deck: List[Card]) = deck contains this
6+
}
7+
8+
def main(args: Array[String]) = {
9+
implicit val deck = List(Card(1, "clubs"))
10+
implicit def intToCard(n: Int): Card = Card(n)
11+
assert(1.isInDeck)
12+
}
13+
}

tests/run/t8280.check

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
Int
2-
Int
31
Long
42
Int
53
Int
64
Int
75
Int
6+
Int

tests/run/t8280.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ object Moop1 {
3737
implicit object f1 extends (Int => String) { def apply(x: Int): String = "Int" }
3838
implicit val f2: Long => String = _ => "Long"
3939

40-
println(5: String)
40+
//println(5: String)
41+
// This picked f1 before, but is now disallowed since subtypes of functions are no longer implicit conversions
4142
}
4243
}
4344

@@ -73,14 +74,14 @@ object Moop3 {
7374
// Dotty deviation. This fails for Dotty with ambiguity error for similar reasons as ob1.
7475
}
7576
object ob2 {
76-
implicit val f1: Int => String = _ => "Int"
77+
implicit val f1: ImplicitConverter[Int, String] = _ => "Int"
7778
implicit def f2(x: Long): String = "Long"
7879

7980
println(5: String)
8081
}
8182
object ob3 {
82-
implicit val f1: Int => String = _ => "Int"
83-
implicit val f2: Long => String = _ => "Long"
83+
implicit val f1: ImplicitConverter[Int, String] = _ => "Int"
84+
implicit val f2: ImplicitConverter[Long, String] = _ => "Long"
8485

8586
println(5: String)
8687
}

0 commit comments

Comments
 (0)