Skip to content

Commit 2e28835

Browse files
committed
Tweaks to given priority
- Don't treat givens as higher priority than implicits. The previous logic was faulty anyway. - Drop special handling of NotGiven in prioritization. The previous logic pretended to do so, but was ineffective. - Don't use old priority in general for implicit/implicit pairs. This would make upgrading to givens a constant struggle. But do use it as a tie breaker in the case of ambiguities. - Also use owner score as a tie breaker if after all other tests we still have an ambiguity.
1 parent 3dfd762 commit 2e28835

File tree

14 files changed

+131
-54
lines changed

14 files changed

+131
-54
lines changed

compiler/src/dotty/tools/dotc/printing/Formatting.scala

+2-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package dotty.tools
22
package dotc
33
package printing
44

5-
import scala.language.unsafeNulls
6-
75
import scala.collection.mutable
86

97
import core.*
@@ -77,12 +75,10 @@ object Formatting {
7775
given [K: Show, V: Show]: Show[Map[K, V]] with
7876
def show(x: Map[K, V]) =
7977
CtxShow(x.map((k, v) => s"${toStr(k)} => ${toStr(v)}"))
80-
end given
8178

8279
given [H: Show, T <: Tuple: Show]: Show[H *: T] with
8380
def show(x: H *: T) =
8481
CtxShow(toStr(x.head) *: toShown(x.tail).asInstanceOf[Tuple])
85-
end given
8682

8783
given [X: Show]: Show[X | Null] with
8884
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
@@ -148,8 +144,8 @@ object Formatting {
148144
private def treatArg(arg: Shown, suffix: String)(using Context): (String, String) = arg.runCtxShow match {
149145
case arg: Seq[?] if suffix.indexOf('%') == 0 && suffix.indexOf('%', 1) != -1 =>
150146
val end = suffix.indexOf('%', 1)
151-
val sep = StringContext.processEscapes(suffix.substring(1, end))
152-
(arg.mkString(sep), suffix.substring(end + 1))
147+
val sep = StringContext.processEscapes(suffix.substring(1, end).nn)
148+
(arg.mkString(sep), suffix.substring(end + 1).nn)
153149
case arg: Seq[?] =>
154150
(arg.map(showArg).mkString("[", ", ", "]"), suffix)
155151
case arg =>

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

+32-25
Original file line numberDiff line numberDiff line change
@@ -1830,15 +1830,13 @@ trait Applications extends Compatibility {
18301830
isAsGood(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
18311831
}
18321832
case _ => // (3)
1833-
def compareValues(tp1: Type, tp2: Type)(using Context) =
1834-
isAsGoodValueType(tp1, tp2, alt1.symbol.is(Implicit), alt2.symbol.is(Implicit))
18351833
tp2 match
18361834
case tp2: MethodType => true // (3a)
18371835
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
18381836
case tp2: PolyType => // (3b)
1839-
explore(compareValues(tp1, instantiateWithTypeVars(tp2)))
1837+
explore(isAsGoodValueType(tp1, instantiateWithTypeVars(tp2)))
18401838
case _ => // 3b)
1841-
compareValues(tp1, tp2)
1839+
isAsGoodValueType(tp1, tp2)
18421840
}
18431841

18441842
/** Test whether value type `tp1` is as good as value type `tp2`.
@@ -1868,15 +1866,14 @@ trait Applications extends Compatibility {
18681866
* for overloading resolution (when `preferGeneral is false), and the opposite relation
18691867
* `U <: T` or `U convertible to `T` for implicit disambiguation between givens
18701868
* (when `preferGeneral` is true). For old-style implicit values, the 3.4 behavior is kept.
1871-
* If one of the alternatives is an implicit and the other is a given (or an extension), the implicit loses.
18721869
*
18731870
* - In Scala 3.5 and Scala 3.6-migration, we issue a warning if the result under
18741871
* Scala 3.6 differ wrt to the old behavior up to 3.5.
18751872
*
18761873
* Also and only for given resolution: If a compared type refers to a given or its module class, use
18771874
* the intersection of its parent classes instead.
18781875
*/
1879-
def isAsGoodValueType(tp1: Type, tp2: Type, alt1IsImplicit: Boolean, alt2IsImplicit: Boolean)(using Context): Boolean =
1876+
def isAsGoodValueType(tp1: Type, tp2: Type)(using Context): Boolean =
18801877
val oldResolution = ctx.mode.is(Mode.OldImplicitResolution)
18811878
if !preferGeneral || Feature.migrateTo3 && oldResolution then
18821879
// Normal specificity test for overloading resolution (where `preferGeneral` is false)
@@ -1892,10 +1889,7 @@ trait Applications extends Compatibility {
18921889
val tp1p = prepare(tp1)
18931890
val tp2p = prepare(tp2)
18941891

1895-
if Feature.sourceVersion.isAtMost(SourceVersion.`3.4`)
1896-
|| oldResolution
1897-
|| alt1IsImplicit && alt2IsImplicit
1898-
then
1892+
if Feature.sourceVersion.isAtMost(SourceVersion.`3.4`) || oldResolution then
18991893
// Intermediate rules: better means specialize, but map all type arguments downwards
19001894
// These are enabled for 3.0-3.5, and for all comparisons between old-style implicits,
19011895
// and in 3.5 and 3.6-migration when we compare with previous rules.
@@ -1909,9 +1903,8 @@ trait Applications extends Compatibility {
19091903
case _ => mapOver(t)
19101904
(flip(tp1p) relaxed_<:< flip(tp2p)) || viewExists(tp1, tp2)
19111905
else
1912-
// New rules: better means generalize, givens (and extensions) always beat implicits
1913-
if alt1IsImplicit != alt2IsImplicit then alt2IsImplicit
1914-
else (tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
1906+
// New rules: better means generalize
1907+
(tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
19151908
end isAsGoodValueType
19161909

19171910
/** Widen the result type of synthetic given methods from the implementation class to the
@@ -1970,8 +1963,9 @@ trait Applications extends Compatibility {
19701963
else if winsPrefix1 then 1
19711964
else -1
19721965

1966+
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1967+
19731968
def compareWithTypes(tp1: Type, tp2: Type) =
1974-
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
19751969
val winsType1 = isAsGood(alt1, tp1, alt2, tp2)
19761970
val winsType2 = isAsGood(alt2, tp2, alt1, tp1)
19771971

@@ -1982,15 +1976,27 @@ trait Applications extends Compatibility {
19821976
// alternatives are the same after following ExprTypes, pick one of them
19831977
// (prefer the one that is not a method, but that's arbitrary).
19841978
if alt1.widenExpr =:= alt2 then -1 else 1
1985-
else ownerScore match
1986-
case 1 => if winsType1 || !winsType2 then 1 else 0
1987-
case -1 => if winsType2 || !winsType1 then -1 else 0
1988-
case 0 =>
1989-
if winsType1 != winsType2 then if winsType1 then 1 else -1
1990-
else if alt1.symbol == alt2.symbol then comparePrefixes
1991-
else 0
1979+
else
1980+
ownerScore match
1981+
case 1 => if winsType1 || !winsType2 then 1 else 0
1982+
case -1 => if winsType2 || !winsType1 then -1 else 0
1983+
case 0 =>
1984+
if winsType1 != winsType2 then if winsType1 then 1 else -1
1985+
else if alt1.symbol == alt2.symbol then comparePrefixes
1986+
else 0
19921987
end compareWithTypes
19931988

1989+
// For implicit resolution, take ownerscore as more significant than type resolution
1990+
// Reason: People use owner hierarchies to explicitly prioritize, we should not
1991+
// break that by changing implicit priority of types. On the other hand, we do
1992+
// want to exhaust all other possibilities before using owner score as a tie breaker.
1993+
// For instance, pos/scala-uri.scala depends on that.
1994+
def drawOrOwner =
1995+
if preferGeneral && !ctx.mode.is(Mode.OldImplicitResolution) then
1996+
//println(i"disambi compare($alt1, $alt2)? $ownerScore")
1997+
ownerScore
1998+
else 0
1999+
19942000
if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1
19952001
else if alt2.symbol.is(ConstructorProxy) && !alt1.symbol.is(ConstructorProxy) then 1
19962002
else
@@ -2000,11 +2006,12 @@ trait Applications extends Compatibility {
20002006
val strippedType2 = stripImplicit(fullType2)
20012007

20022008
val result = compareWithTypes(strippedType1, strippedType2)
2003-
if (result != 0) result
2004-
else if (strippedType1 eq fullType1)
2005-
if (strippedType2 eq fullType2) 0 // no implicits either side: its' a draw
2009+
if result != 0 then result
2010+
else if strippedType1 eq fullType1 then
2011+
if strippedType2 eq fullType2
2012+
then drawOrOwner // no implicits either side: its' a draw
20062013
else 1 // prefer 1st alternative with no implicits
2007-
else if (strippedType2 eq fullType2) -1 // prefer 2nd alternative with no implicits
2014+
else if strippedType2 eq fullType2 then -1 // prefer 2nd alternative with no implicits
20082015
else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters
20092016
}
20102017
end compare

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -1330,10 +1330,13 @@ trait Implicits:
13301330
if alt1.ref eq alt2.ref then 0
13311331
else if alt1.level != alt2.level then alt1.level - alt2.level
13321332
else
1333-
var cmp = comp(using searchContext())
1333+
lazy val prev = comp(using searchContext().addMode(Mode.OldImplicitResolution))
1334+
val cmp = comp(using searchContext()) match
1335+
// if we get an ambiguity with new rules for a pair of old-style implicits, fall back to old rules
1336+
case 0 if alt1.ref.symbol.is(Implicit) && alt2.ref.symbol.is(Implicit) => prev
1337+
case cmp => cmp
13341338
val sv = Feature.sourceVersion
13351339
if isWarnPriorityChangeVersion(sv) then
1336-
val prev = comp(using searchContext().addMode(Mode.OldImplicitResolution))
13371340
if disambiguate && cmp != prev then
13381341
def warn(msg: Message) =
13391342
val critical = alt1.ref :: alt2.ref :: Nil

compiler/test/dotty/tools/dotc/StringFormatterTest.scala

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class StringFormatterTest extends AbstractStringFormatterTest:
2323
@Test def flagsTup = check("(<static>,final)", i"${(JavaStatic, Final)}")
2424
@Test def seqOfTup2 = check("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %")
2525
@Test def seqOfTup3 = check("(Foo,given, (right is approximated))", i"${Seq((Foo, Given, TypeComparer.ApproxState.None.addHigh))}%, %")
26+
@Test def tupleNull = check("(1,null)", i"${(1, null: String | Null)}")
2627

2728
class StorePrinter extends Printer:
2829
var string: String = "<never set>"

tests/neg/i2974.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
trait Foo[-T]
3+
trait Bar[-T] extends Foo[T]
4+
5+
object Test {
6+
7+
locally:
8+
implicit val fa: Foo[Int] = ???
9+
implicit val ba: Bar[Int] = ???
10+
summon[Foo[Int]] // ok
11+
12+
locally:
13+
implicit val fa: Foo[Int] = ???
14+
implicit val ba: Bar[Any] = ???
15+
summon[Foo[Int]] // ok
16+
17+
locally:
18+
given fa: Foo[Any] = ???
19+
given ba: Bar[Int] = ???
20+
summon[Foo[Int]] // error: now ambiguous,
21+
// was resolving to `ba` when using intermediate rules:
22+
// better means specialize, but map all type arguments downwards
23+
24+
locally:
25+
implicit val fa: Foo[Any] = ???
26+
implicit val ba: Bar[Int] = ???
27+
summon[Foo[Int]] // is OK since we fall back to old rules for old-style implicits as a tie breaker
28+
}

tests/pos/given-priority.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* These tests show various mechanisms available for implicit prioritization.
2+
*/
3+
import language.`3.6`
4+
5+
class A // The type for which we infer terms below
6+
class B extends A
7+
8+
/* First, two schemes that require a pre-planned architecture for how and
9+
* where given instances are defined.
10+
*
11+
* Traditional scheme: prioritize with location in class hierarchy
12+
*/
13+
class LowPriorityImplicits:
14+
given g1: A()
15+
16+
object NormalImplicits extends LowPriorityImplicits:
17+
given g2: B()
18+
19+
def test1 =
20+
import NormalImplicits.given
21+
val x = summon[A]
22+
val _: B = x
23+
val y = summon[B]
24+
val _: B = y

tests/pos/i2974.scala

-12
This file was deleted.

tests/pos/scala-uri.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import scala.language.implicitConversions
2+
3+
trait QueryKey[A]
4+
object QueryKey extends QueryKeyInstances
5+
sealed trait QueryKeyInstances:
6+
implicit val stringQueryKey: QueryKey[String] = ???
7+
8+
trait QueryValue[-A]
9+
object QueryValue extends QueryValueInstances
10+
sealed trait QueryValueInstances1:
11+
implicit final val stringQueryValue: QueryValue[String] = ???
12+
implicit final val noneQueryValue: QueryValue[None.type] = ???
13+
14+
sealed trait QueryValueInstances extends QueryValueInstances1:
15+
implicit final def optionQueryValue[A: QueryValue]: QueryValue[Option[A]] = ???
16+
17+
trait QueryKeyValue[A]
18+
object QueryKeyValue:
19+
implicit def tuple2QueryKeyValue[K: QueryKey, V: QueryValue]: QueryKeyValue[(K, V)] = ???
20+
21+
22+
@main def Test = summon[QueryKeyValue[(String, None.type)]]
23+
24+
/*(using
25+
QueryKeyValue.tuple2QueryKeyValue[String, None.type](
26+
QueryKey.stringQueryKey,
27+
QueryValue.optionQueryValue[A]))*/
28+
29+
// error

tests/run/enrich-gentraversable.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ List(2, 4)
44
HW
55
ArraySeq(72, 108, 108, 32, 114, 108, 100)
66
Map(bar -> 2)
7-
TreeMap(bar -> 2)
7+
Map(bar -> 2)
88
Map(bar -> 2)

tests/run/enrich-gentraversable.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ object Test extends App {
4747

4848
val tm = TreeMap(1 -> "foo", 2 -> "bar")
4949
val tmm = tm.filterMap { case (k, v) => if (k % 2 == 0) Some(v -> k) else None }
50-
typed[TreeMap[String, Int]](tmm)
50+
typed[Map[String, Int]](tmm) // was TreeMap, now Map,
51+
// since `BuildFrom` is covariant in `That` and `TreeMap[String, Int] <:< Map[String, Int]`
5152
println(tmm)
5253

5354
val mv = m.view

tests/run/implicits_poly.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
barFoo
1+
fooFoo

tests/warn/i15503a.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ object GivenImportOrderAtoB:
149149
object A { implicit val x: X = new X }
150150
object B { implicit val y: Y = new Y }
151151
class C {
152-
import A._ // warn
153-
import B._ // OK
152+
import A._ // OK
153+
import B._ // warn
154154
def t = implicitly[X]
155155
}
156156

@@ -160,8 +160,8 @@ object GivenImportOrderBtoA:
160160
object A { implicit val x: X = new X }
161161
object B { implicit val y: Y = new Y }
162162
class C {
163-
import B._ // OK
164-
import A._ // warn
163+
import B._ // warn
164+
import A._ // OK
165165
def t = implicitly[X]
166166
}
167167
/* END : tests on given import order */

0 commit comments

Comments
 (0)