Skip to content

Commit f07697b

Browse files
committed
Disallow subtypes of Function1 acting as implicit conversions
The new test `falseView.scala` shows the problem. We might create an implicit value of some type that happens to be a subtype of Function1. We might now expect that this gives us an implicit conversion, this is most often unintended and surprising. See the comment in Implicits#discardForView for a discussion why we picked the particular scheme implemented here.
1 parent c844809 commit f07697b

File tree

6 files changed

+42
-8
lines changed

6 files changed

+42
-8
lines changed

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

Lines changed: 2 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")

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,33 @@ 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. 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:
102+
//
103+
// implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a)
104+
//
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.
108+
// The reason for leaving out `Predef_conforms` is that we know it adds
109+
// nothing since it only relates subtype with supertype.
110+
!tpw.isRef(defn.FunctionClass(1)) &&
111+
(!tpw.derivesFrom(defn.Predef_Conforms) || ref.symbol == defn.Predef_conforms)
90112
}
91113

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

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+
}

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/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: 2 additions & 1 deletion
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

0 commit comments

Comments
 (0)