Skip to content

Commit f267b27

Browse files
authored
Merge pull request #4738 from dotty-staging/fix-#876
Fix #876: Allow implicit conversions from singleton types
2 parents bdf3ef4 + 673af1f commit f267b27

File tree

7 files changed

+65
-18
lines changed

7 files changed

+65
-18
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ object Mode {
3737
* that TypeParamRefs can be sub- and supertypes of anything. See TypeComparer.
3838
*/
3939
val TypevarsMissContext = newMode(4, "TypevarsMissContext")
40+
4041
val CheckCyclic = newMode(5, "CheckCyclic")
4142

4243
/** We are looking at the arguments of a supercall */

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -598,15 +598,6 @@ trait Checking {
598598
defn.ObjectType
599599
}
600600

601-
/** Check that a non-implicit parameter making up the first parameter section of an
602-
* implicit conversion is not a singleton type.
603-
*/
604-
def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = vparamss match {
605-
case (vparam :: Nil) :: _ if !(vparam.symbol is Implicit) =>
606-
checkNotSingleton(vparam.tpt, " to be parameter type of an implicit conversion")
607-
case _ =>
608-
}
609-
610601
/** If `sym` is an implicit conversion, check that implicit conversions are enabled.
611602
* @pre sym.is(Implicit)
612603
*/
@@ -948,7 +939,6 @@ trait NoChecking extends ReChecking {
948939
override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
949940
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
950941
override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp
951-
override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
952942
override def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = ()
953943
override def checkImplicitConversionUseOK(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = ()
954944
override def checkFeasibleParent(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp

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

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ object Implicits {
7171
/** The implicit references */
7272
def refs: List[ImplicitRef]
7373

74+
private var SingletonClass: ClassSymbol = null
75+
76+
/** Widen type so that it is neither a singleton type nor a type that inherits from scala.Singleton. */
77+
private def widenSingleton(tp: Type)(implicit ctx: Context): Type = {
78+
if (SingletonClass == null) SingletonClass = defn.SingletonClass
79+
val wtp = tp.widenSingleton
80+
if (wtp.derivesFrom(SingletonClass)) defn.AnyType else wtp
81+
}
82+
7483
/** Return those references in `refs` that are compatible with type `pt`. */
7584
protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") {
7685

@@ -80,15 +89,17 @@ object Implicits {
8089
case mt: MethodType =>
8190
mt.isImplicitMethod ||
8291
mt.paramInfos.lengthCompare(1) != 0 ||
83-
!ctx.test(implicit ctx => argType relaxed_<:< mt.paramInfos.head)
92+
!ctx.test(implicit ctx =>
93+
argType relaxed_<:< widenSingleton(mt.paramInfos.head))
8494
case poly: PolyType =>
8595
// We do not need to call ProtoTypes#constrained on `poly` because
8696
// `refMatches` is always called with mode TypevarsMissContext enabled.
8797
poly.resultType match {
8898
case mt: MethodType =>
8999
mt.isImplicitMethod ||
90100
mt.paramInfos.length != 1 ||
91-
!ctx.test(implicit ctx => argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty))
101+
!ctx.test(implicit ctx =>
102+
argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head), null, Set.empty))
92103
case rtp =>
93104
discardForView(wildApprox(rtp, null, Set.empty), argType)
94105
}
@@ -132,14 +143,32 @@ object Implicits {
132143
case _ => false
133144
}
134145

146+
/** Widen singleton arguments of implicit conversions to their underlying type.
147+
* This is necessary so that they can be found eligible for the argument type.
148+
* Note that we always take the underlying type of a singleton type as the argument
149+
* type, so that we get a reasonable implicit cache hit ratio.
150+
*/
151+
def adjustSingletonArg(tp: Type): Type = tp match {
152+
case tp: PolyType =>
153+
val res = adjustSingletonArg(tp.resType)
154+
if (res `eq` tp.resType) tp else tp.derivedLambdaType(resType = res)
155+
case tp: MethodType =>
156+
tp.derivedLambdaType(paramInfos = tp.paramInfos.mapConserve(widenSingleton))
157+
case _ => tp
158+
}
159+
135160
(ref.symbol isAccessibleFrom ref.prefix) && {
136161
if (discard) {
137162
record("discarded eligible")
138163
false
139164
}
140165
else {
141166
val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749
142-
NoViewsAllowed.isCompatible(normalize(ref, pt), ptNorm)
167+
val refAdjusted =
168+
if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref.widenSingleton)
169+
else ref
170+
val refNorm = normalize(refAdjusted, pt)
171+
NoViewsAllowed.isCompatible(refNorm, ptNorm)
143172
}
144173
}
145174
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,10 +1413,7 @@ class Typer extends Namer
14131413
val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef])
14141414
val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef])
14151415
vparamss1.foreach(checkNoForwardDependencies)
1416-
if (sym is Implicit) {
1417-
checkImplicitParamsNotSingletons(vparamss1)
1418-
checkImplicitConversionDefOK(sym)
1419-
}
1416+
if (sym is Implicit) checkImplicitConversionDefOK(sym)
14201417
val tpt1 = checkSimpleKinded(typedType(tpt))
14211418

14221419
var rhsCtx = ctx

tests/neg/i876.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test {
2+
val a: Int = 1
3+
implicit def foo(x: a.type): String = "hi"
4+
val b: Int = a
5+
6+
val x: String = a // ok
7+
val y: String = b // error: found Int, required String
8+
}

tests/neg/implicitDefs.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object implicitDefs {
77

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

1212
def foo(implicit x: String) = 1
1313

tests/run/i876.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
object Test extends App {
2+
object O
3+
implicit def foo(x: O.type): String = "hello"
4+
val s: String = O
5+
implicit def bar(x: s.type): Int = s.length
6+
//implicit def bar2(x: String): Int = s.length
7+
val l: Int = s
8+
assert(s == "hello")
9+
assert(l == 5)
10+
}
11+
12+
object Test3781 {
13+
class Foo[T](val value : T)
14+
object Foo {
15+
implicit def fromXInt[T <: Int with Singleton](i : T): Foo[T] = new Foo[T](i)
16+
}
17+
class FooUser[T] {
18+
def op[T2](that : Foo[T2]) : FooUser[T2] = new FooUser[T2]
19+
}
20+
val f = new FooUser[1]
21+
val f2 = f op 2
22+
}

0 commit comments

Comments
 (0)