From f07697b25294eaafb1c86698c44a699ec1c0d1ba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Mar 2017 11:35:05 +0100 Subject: 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. --- .../src/dotty/tools/dotc/core/Definitions.scala | 2 ++ .../src/dotty/tools/dotc/typer/Implicits.scala | 32 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) (limited to 'compiler/src/dotty/tools/dotc') diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3cab75f93..fcbb2f974 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -299,6 +299,8 @@ class Definitions { lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol + lazy val Predef_ConformsR = ScalaPredefModule.requiredClass("$less$colon$less").typeRef + def Predef_Conforms(implicit ctx: Context) = Predef_ConformsR.symbol lazy val Predef_conformsR = ScalaPredefModule.requiredMethodRef("$conforms") def Predef_conforms(implicit ctx: Context) = Predef_conformsR.symbol lazy val Predef_classOfR = ScalaPredefModule.requiredMethodRef("classOf") diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 759cc62e9..933e26564 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -82,11 +82,33 @@ object Implicits { case tpw: TermRef => false // can't discard overloaded refs case tpw => - //if (ctx.typer.isApplicable(tp, argType :: Nil, resultType)) - // println(i"??? $tp is applicable to $this / typeSymbol = ${tpw.typeSymbol}") - !tpw.derivesFrom(defn.FunctionClass(1)) || - ref.symbol == defn.Predef_conforms // - // as an implicit conversion, Predef.$conforms is a no-op, so exclude it + // Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views. + // However, Predef.$conforms is not eligible, because it is a no-op. + // + // In principle, it would be cleanest if only implicit methods qualified + // as implicit conversions. The reasons for deviating are as follows: + // Keeping Function1: It's still used quite often (for instance, view + // bounds map to implicits of function types) and there is no feasible workaround. + // One tempting workaround would be to add a global def + // + // implicit def convertIfFuntion1[A, B](x: A)(implicit ev: A => B): B = ev(a) + // + // But that would throw out the baby with the bathwater. Now, every subtype of + // function gives again rise to an implicit conversion. So it's better to just accept + // function types in their dual roles. + // + // The reason for the treatment of <:< and conforms is similar. We could + // avoid the clause by having a standard conversion like this in Predef: + // + // implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a) + // + // But that would slow down implicit search a lot, because this conversion is + // eligible for all pairs of types, and therefore is tried a lot. So we + // emulate the existence of a such a conversion directly in the search. + // The reason for leaving out `Predef_conforms` is that we know it adds + // nothing since it only relates subtype with supertype. + !tpw.isRef(defn.FunctionClass(1)) && + (!tpw.derivesFrom(defn.Predef_Conforms) || ref.symbol == defn.Predef_conforms) } def discardForValueType(tpw: Type): Boolean = tpw match { -- cgit v1.2.3