Skip to content

Commit

Permalink
Merge pull request #4738 from dotty-staging/fix-#876
Browse files Browse the repository at this point in the history
Fix #876: Allow implicit conversions from singleton types
  • Loading branch information
odersky authored Jun 29, 2018
2 parents bdf3ef4 + 673af1f commit f267b27
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 18 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ object Mode {
* that TypeParamRefs can be sub- and supertypes of anything. See TypeComparer.
*/
val TypevarsMissContext = newMode(4, "TypevarsMissContext")

val CheckCyclic = newMode(5, "CheckCyclic")

/** We are looking at the arguments of a supercall */
Expand Down
10 changes: 0 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -598,15 +598,6 @@ trait Checking {
defn.ObjectType
}

/** Check that a non-implicit parameter making up the first parameter section of an
* implicit conversion is not a singleton type.
*/
def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = vparamss match {
case (vparam :: Nil) :: _ if !(vparam.symbol is Implicit) =>
checkNotSingleton(vparam.tpt, " to be parameter type of an implicit conversion")
case _ =>
}

/** If `sym` is an implicit conversion, check that implicit conversions are enabled.
* @pre sym.is(Implicit)
*/
Expand Down Expand Up @@ -948,7 +939,6 @@ trait NoChecking extends ReChecking {
override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp
override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
override def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = ()
override def checkImplicitConversionUseOK(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = ()
override def checkFeasibleParent(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp
Expand Down
35 changes: 32 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ object Implicits {
/** The implicit references */
def refs: List[ImplicitRef]

private var SingletonClass: ClassSymbol = null

/** Widen type so that it is neither a singleton type nor a type that inherits from scala.Singleton. */
private def widenSingleton(tp: Type)(implicit ctx: Context): Type = {
if (SingletonClass == null) SingletonClass = defn.SingletonClass
val wtp = tp.widenSingleton
if (wtp.derivesFrom(SingletonClass)) defn.AnyType else wtp
}

/** Return those references in `refs` that are compatible with type `pt`. */
protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") {

Expand All @@ -80,15 +89,17 @@ object Implicits {
case mt: MethodType =>
mt.isImplicitMethod ||
mt.paramInfos.lengthCompare(1) != 0 ||
!ctx.test(implicit ctx => argType relaxed_<:< mt.paramInfos.head)
!ctx.test(implicit ctx =>
argType relaxed_<:< widenSingleton(mt.paramInfos.head))
case poly: PolyType =>
// We do not need to call ProtoTypes#constrained on `poly` because
// `refMatches` is always called with mode TypevarsMissContext enabled.
poly.resultType match {
case mt: MethodType =>
mt.isImplicitMethod ||
mt.paramInfos.length != 1 ||
!ctx.test(implicit ctx => argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty))
!ctx.test(implicit ctx =>
argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head), null, Set.empty))
case rtp =>
discardForView(wildApprox(rtp, null, Set.empty), argType)
}
Expand Down Expand Up @@ -132,14 +143,32 @@ object Implicits {
case _ => false
}

/** Widen singleton arguments of implicit conversions to their underlying type.
* This is necessary so that they can be found eligible for the argument type.
* Note that we always take the underlying type of a singleton type as the argument
* type, so that we get a reasonable implicit cache hit ratio.
*/
def adjustSingletonArg(tp: Type): Type = tp match {
case tp: PolyType =>
val res = adjustSingletonArg(tp.resType)
if (res `eq` tp.resType) tp else tp.derivedLambdaType(resType = res)
case tp: MethodType =>
tp.derivedLambdaType(paramInfos = tp.paramInfos.mapConserve(widenSingleton))
case _ => tp
}

(ref.symbol isAccessibleFrom ref.prefix) && {
if (discard) {
record("discarded eligible")
false
}
else {
val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749
NoViewsAllowed.isCompatible(normalize(ref, pt), ptNorm)
val refAdjusted =
if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref.widenSingleton)
else ref
val refNorm = normalize(refAdjusted, pt)
NoViewsAllowed.isCompatible(refNorm, ptNorm)
}
}
}
Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1413,10 +1413,7 @@ class Typer extends Namer
val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef])
val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef])
vparamss1.foreach(checkNoForwardDependencies)
if (sym is Implicit) {
checkImplicitParamsNotSingletons(vparamss1)
checkImplicitConversionDefOK(sym)
}
if (sym is Implicit) checkImplicitConversionDefOK(sym)
val tpt1 = checkSimpleKinded(typedType(tpt))

var rhsCtx = ctx
Expand Down
8 changes: 8 additions & 0 deletions tests/neg/i876.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
object Test {
val a: Int = 1
implicit def foo(x: a.type): String = "hi"
val b: Int = a

val x: String = a // ok
val y: String = b // error: found Int, required String
}
2 changes: 1 addition & 1 deletion tests/neg/implicitDefs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object implicitDefs {

implicit val x = 2 // error: type of implicit definition needs to be given explicitly
implicit def y(x: Int) = 3 // error: result type of implicit definition needs to be given explicitly
implicit def z(a: x.type): String = "" // error: implicit conversion may not have a parameter of singleton type
implicit def z(a: x.type): String = "" // ok, used to be: implicit conversion may not have a parameter of singleton type

def foo(implicit x: String) = 1

Expand Down
22 changes: 22 additions & 0 deletions tests/run/i876.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
object Test extends App {
object O
implicit def foo(x: O.type): String = "hello"
val s: String = O
implicit def bar(x: s.type): Int = s.length
//implicit def bar2(x: String): Int = s.length
val l: Int = s
assert(s == "hello")
assert(l == 5)
}

object Test3781 {
class Foo[T](val value : T)
object Foo {
implicit def fromXInt[T <: Int with Singleton](i : T): Foo[T] = new Foo[T](i)
}
class FooUser[T] {
def op[T2](that : Foo[T2]) : FooUser[T2] = new FooUser[T2]
}
val f = new FooUser[1]
val f2 = f op 2
}

0 comments on commit f267b27

Please sign in to comment.