diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 020b884b22b3..e689544503db 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -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 */ diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e909906a8b7a..47893a9390c1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -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) */ @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 31fd2d2026e5..848d928d7cf5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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") { @@ -80,7 +89,8 @@ 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. @@ -88,7 +98,8 @@ object Implicits { 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) } @@ -132,6 +143,20 @@ 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") @@ -139,7 +164,11 @@ object Implicits { } 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) } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fa48b6315a63..115c4f8130a8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 diff --git a/tests/neg/i876.scala b/tests/neg/i876.scala new file mode 100644 index 000000000000..9affe2a5a607 --- /dev/null +++ b/tests/neg/i876.scala @@ -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 +} diff --git a/tests/neg/implicitDefs.scala b/tests/neg/implicitDefs.scala index 3bfe604341a3..a48d533d4d9f 100644 --- a/tests/neg/implicitDefs.scala +++ b/tests/neg/implicitDefs.scala @@ -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 diff --git a/tests/run/i876.scala b/tests/run/i876.scala new file mode 100644 index 000000000000..7480e3605e6f --- /dev/null +++ b/tests/run/i876.scala @@ -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 +} \ No newline at end of file