Skip to content

Commit 80c2fbd

Browse files
committed
Merge pull request #902 from dotty-staging/fix-numeric-implicit-args
Fix numeric implicit args
2 parents 9cbe994 + 3460856 commit 80c2fbd

File tree

7 files changed

+55
-12
lines changed

7 files changed

+55
-12
lines changed

src/dotty/tools/dotc/core/Types.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ object Types {
606606
}
607607

608608
/** Is this type a primitive value type which can be widened to the primitive value type `that`? */
609-
def isValueSubType(that: Type)(implicit ctx: Context) = widenExpr match {
609+
def isValueSubType(that: Type)(implicit ctx: Context) = widen match {
610610
case self: TypeRef if defn.ScalaValueClasses contains self.symbol =>
611611
that.widenExpr match {
612612
case that: TypeRef if defn.ScalaValueClasses contains that.symbol =>
@@ -618,6 +618,9 @@ object Types {
618618
false
619619
}
620620

621+
def relaxed_<:<(that: Type)(implicit ctx: Context) =
622+
(this <:< that) || (this isValueSubType that)
623+
621624
/** Is this type a legal type for a member that overrides another
622625
* member of type `that`? This is the same as `<:<`, except that
623626
* the types ()T and => T are identified, and T is seen as overriding

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

+26-4
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,19 @@ object Implicits {
4444
/** Return those references in `refs` that are compatible with type `pt`. */
4545
protected def filterMatching(pt: Type)(implicit ctx: Context): List[TermRef] = track("filterMatching") {
4646

47-
def refMatches(ref: TermRef)(implicit ctx: Context) = {
47+
def refMatches(ref: TermRef)(implicit ctx: Context) = /*ctx.traceIndented(i"refMatches $ref $pt")*/ {
4848

4949
def discardForView(tpw: Type, argType: Type): Boolean = tpw match {
5050
case mt: MethodType =>
5151
mt.isImplicit ||
5252
mt.paramTypes.length != 1 ||
53-
!(argType <:< mt.paramTypes.head)(ctx.fresh.setExploreTyperState)
53+
!(argType relaxed_<:< mt.paramTypes.head)(ctx.fresh.setExploreTyperState)
5454
case poly: PolyType =>
5555
poly.resultType match {
5656
case mt: MethodType =>
5757
mt.isImplicit ||
5858
mt.paramTypes.length != 1 ||
59-
!(argType <:< wildApprox(mt.paramTypes.head)(ctx.fresh.setExploreTyperState))
59+
!(argType relaxed_<:< wildApprox(mt.paramTypes.head)(ctx.fresh.setExploreTyperState))
6060
case rtp =>
6161
discardForView(wildApprox(rtp), argType)
6262
}
@@ -532,6 +532,25 @@ trait Implicits { self: Typer =>
532532
case nil => acc
533533
}
534534

535+
/** If the (result types of) the expected type, and both alternatives
536+
* are all numeric value types, return the alternative which has
537+
* the smaller numeric subtype as result type, if it exists.
538+
* (This alternative is then discarded).
539+
*/
540+
def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess): SearchResult = {
541+
def isNumeric(tp: Type) = tp.typeSymbol.isNumericValueClass
542+
def isProperSubType(tp1: Type, tp2: Type) =
543+
tp1.isValueSubType(tp2) && !tp2.isValueSubType(tp1)
544+
val rpt = pt.resultType
545+
val rt1 = alt1.ref.widen.resultType
546+
val rt2 = alt2.ref.widen.resultType
547+
if (isNumeric(rpt) && isNumeric(rt1) && isNumeric(rt2))
548+
if (isProperSubType(rt1, rt2)) alt1
549+
else if (isProperSubType(rt2, rt1)) alt2
550+
else NoImplicitMatches
551+
else NoImplicitMatches
552+
}
553+
535554
/** Convert a (possibly empty) list of search successes into a single search result */
536555
def condense(hits: List[SearchSuccess]): SearchResult = hits match {
537556
case best :: alts =>
@@ -541,7 +560,10 @@ trait Implicits { self: Typer =>
541560
println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}")
542561
isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState)
543562
*/
544-
new AmbiguousImplicits(best.ref, alt.ref, pt, argument)
563+
numericValueTieBreak(best, alt) match {
564+
case eliminated: SearchSuccess => condense(hits.filter(_ ne eliminated))
565+
case _ => new AmbiguousImplicits(best.ref, alt.ref, pt, argument)
566+
}
545567
case None =>
546568
ctx.runInfo.useCount(best.ref) += 1
547569
best

src/dotty/tools/dotc/typer/ProtoTypes.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ object ProtoTypes {
3232
* 1. `tp` is a subtype of `pt`
3333
* 2. `pt` is by name parameter type, and `tp` is compatible with its underlying type
3434
* 3. there is an implicit conversion from `tp` to `pt`.
35+
* 4. `tp` is a numeric subtype of `pt` (this case applies even if implicit conversions are disabled)
3536
*/
3637
def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
37-
tp.widenExpr <:< pt.widenExpr || viewExists(tp, pt)
38+
(tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt)
3839

3940
/** Test compatibility after normalization in a fresh typerstate. */
4041
def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = {

tests/new/implicits.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Test {
2+
3+
class X(i: Int)
4+
5+
implicit def int2x(i: Int): X = new X(i)
6+
7+
val x: X = Byte.MinValue
8+
9+
}

tests/pos/implicits1.scala

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ object Implicits {
3636

3737
val d: Int = z.foo("abc")
3838

39+
val x: X = Byte.MinValue
40+
3941
//import X.BarDeco
4042

4143
println(z.bar)
@@ -50,4 +52,6 @@ object Implicits {
5052

5153
val s: Modifier = Some("rd").getOrElse("")
5254

55+
val xx: Int = (1: Byte)
56+
5357
}

tests/run/t8280.check

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
Int
22
Int
3-
Int
4-
Int
5-
Int
3+
Long
64
Int
75
Int
86
Int

tests/run/t8280.scala

+9-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ object Moop1 {
2020
implicit object f1 extends (Int => String) { def apply(x: Int): String = "Int" }
2121
implicit object f2 extends (Long => String) { def apply(x: Long): String = "Long" }
2222

23-
println(5: String)
23+
// println(5: String)
24+
// Dotty deviation. The above fails for Dotty with ambiguity error.
25+
// Both f1 and f2 are applicable conversions for Int => String. Neither is better than the other.
26+
// Scala2 contains a hack for backwards compatibility, which we are not forced to repeat.
27+
// See discussion under SI-8280.
28+
2429
}
2530
object ob2 {
2631
implicit object f1 extends (Int => String) { def apply(x: Int): String = "Int" }
@@ -42,7 +47,7 @@ object Moop2 {
4247
implicit def f1(x: Int): String = "Int"
4348
implicit object f2 extends (Long => String) { def apply(x: Long): String = "Long" }
4449

45-
println(5: String)
50+
println(5: String) // Dotty deviation: Dotty picks f2, because non-methods are more specific than methods
4651
}
4752
object ob2 {
4853
implicit def f1(x: Int): String = "Int"
@@ -64,7 +69,8 @@ object Moop3 {
6469
implicit val f1: Int => String = _ => "Int"
6570
implicit object f2 extends (Long => String) { def apply(x: Long): String = "Long" }
6671

67-
println(5: String)
72+
// println(5: String)
73+
// Dotty deviation. This fails for Dotty with ambiguity error for similar reasons as ob1.
6874
}
6975
object ob2 {
7076
implicit val f1: Int => String = _ => "Int"

0 commit comments

Comments
 (0)