Skip to content

Commit 3d9ce8e

Browse files
committed
Disambiguate by owner score before considering further implicit arguments
This makes slick-migration-api-example work and makes the original scala-uri.scala fail. See the comment in neg/scala-uri.scala for why this is so. It also needs a change in the givens in dotty.tools.reporting.Formatting. The motivation for honoring owner score over the others is that it is often used for explicit prioritization, so we should take it into account more than other secondary criteria.
1 parent 462b293 commit 3d9ce8e

File tree

6 files changed

+88
-33
lines changed

6 files changed

+88
-33
lines changed

Diff for: compiler/src/dotty/tools/dotc/printing/Formatting.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ object Formatting {
5050
object ShowAny extends Show[Any]:
5151
def show(x: Any): Shown = x
5252

53-
class ShowImplicits3:
53+
class ShowImplicits4:
54+
given [X: Show]: Show[X | Null] with
55+
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
56+
57+
class ShowImplicits3 extends ShowImplicits4:
5458
given Show[Product] = ShowAny
5559

5660
class ShowImplicits2 extends ShowImplicits3:
@@ -80,9 +84,6 @@ object Formatting {
8084
def show(x: H *: T) =
8185
CtxShow(toStr(x.head) *: toShown(x.tail).asInstanceOf[Tuple])
8286

83-
given [X: Show]: Show[X | Null] with
84-
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
85-
8687
given Show[FlagSet] with
8788
def show(x: FlagSet) = x.flagsString
8889

Diff for: compiler/src/dotty/tools/dotc/typer/Applications.scala

+15-21
Original file line numberDiff line numberDiff line change
@@ -1865,7 +1865,7 @@ trait Applications extends Compatibility {
18651865
* - From Scala 3.6, `T <:p U` means `T <: U` or `T` convertible to `U`
18661866
* for overloading resolution (when `preferGeneral is false), and the opposite relation
18671867
* `U <: T` or `U convertible to `T` for implicit disambiguation between givens
1868-
* (when `preferGeneral` is true).
1868+
* (when `preferGeneral` is true).
18691869
*
18701870
* - In Scala 3.5 and Scala 3.6-migration, we issue a warning if the result under
18711871
* Scala 3.6 differ wrt to the old behavior up to 3.5.
@@ -1963,9 +1963,8 @@ trait Applications extends Compatibility {
19631963
else if winsPrefix1 then 1
19641964
else -1
19651965

1966-
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1967-
19681966
def compareWithTypes(tp1: Type, tp2: Type) =
1967+
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
19691968
val winsType1 = isAsGood(alt1, tp1, alt2, tp2)
19701969
val winsType2 = isAsGood(alt2, tp2, alt1, tp1)
19711970

@@ -1977,25 +1976,22 @@ trait Applications extends Compatibility {
19771976
// (prefer the one that is not a method, but that's arbitrary).
19781977
if alt1.widenExpr =:= alt2 then -1 else 1
19791978
else
1979+
// For implicit resolution, take ownerscore as more significant than type resolution
1980+
// Reason: People use owner hierarchies to explicitly prioritize, we should not
1981+
// break that by changing implicit priority of types.
1982+
def drawOrOwner =
1983+
if preferGeneral && !ctx.mode.is(Mode.OldImplicitResolution)
1984+
then ownerScore
1985+
else 0
19801986
ownerScore match
1981-
case 1 => if winsType1 || !winsType2 then 1 else 0
1982-
case -1 => if winsType2 || !winsType1 then -1 else 0
1987+
case 1 => if winsType1 || !winsType2 then 1 else drawOrOwner
1988+
case -1 => if winsType2 || !winsType1 then -1 else drawOrOwner
19831989
case 0 =>
19841990
if winsType1 != winsType2 then if winsType1 then 1 else -1
19851991
else if alt1.symbol == alt2.symbol then comparePrefixes
19861992
else 0
19871993
end compareWithTypes
19881994

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 disambiguateWithOwner(result: Int) =
1995-
if result == 0 && preferGeneral && !ctx.mode.is(Mode.OldImplicitResolution)
1996-
then ownerScore
1997-
else result
1998-
19991995
if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1
20001996
else if alt2.symbol.is(ConstructorProxy) && !alt1.symbol.is(ConstructorProxy) then 1
20011997
else
@@ -2005,14 +2001,12 @@ trait Applications extends Compatibility {
20052001
val strippedType2 = stripImplicit(fullType2)
20062002

20072003
var result = compareWithTypes(strippedType1, strippedType2)
2008-
if result != 0 then return result
2009-
if strippedType1 eq fullType1 then
2010-
if strippedType2 eq fullType2
2011-
then disambiguateWithOwner(0) // no implicits either side: its' a draw
2004+
if result != 0 then result
2005+
else if strippedType1 eq fullType1 then
2006+
if strippedType2 eq fullType2 then 0 // no implicits either side: its' a draw
20122007
else 1 // prefer 1st alternative with no implicits
20132008
else if strippedType2 eq fullType2 then -1 // prefer 2nd alternative with no implicits
2014-
else disambiguateWithOwner(
2015-
compareWithTypes(fullType1, fullType2)) // continue by comparing implicit parameters
2009+
else compareWithTypes(fullType1, fullType2) // continue by comparing implicit parameters
20162010
}
20172011
end compare
20182012

Diff for: tests/neg/scala-uri.check

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- [E172] Type Error: tests/neg/scala-uri.scala:30:59 ------------------------------------------------------------------
2+
30 |@main def Test = summon[QueryKeyValue[(String, None.type)]] // error
3+
| ^
4+
|No best given instance of type QueryKeyValue[(String, None.type)] was found for parameter x of method summon in object Predef.
5+
|I found:
6+
|
7+
| QueryKeyValue.tuple2QueryKeyValue[String, None.type](QueryKey.stringQueryKey,
8+
| QueryValue.optionQueryValue[A](
9+
| /* ambiguous: both value stringQueryValue in trait QueryValueInstances1 and value noneQueryValue in trait QueryValueInstances1 match type QueryValue[A] */
10+
| summon[QueryValue[A]]
11+
| )
12+
| )
13+
|
14+
|But both value stringQueryValue in trait QueryValueInstances1 and value noneQueryValue in trait QueryValueInstances1 match type QueryValue[A].

Diff for: tests/neg/scala-uri.scala

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
// The noneQueryValue makes no sense at this priority. Since QueryValue
14+
// is contravariant, QueryValue[None.type] is always better than QueryValue[Option[A]]
15+
// no matter whether it's old or new resolution. So taking both owner and type
16+
// score into account, it's always a draw. With the new disambiguation, we prefer
17+
// the optionQueryValue[A], which gives an ambiguity down the road, because we don't
18+
// know what the wrapped type A is. Previously, we preferred QueryValue[None.type]
19+
// because it is unconditional. The solution is to put QueryValue[None.type] in the
20+
// same trait as QueryValue[Option[A]], as is shown in pos/scala-uri.scala.
21+
22+
sealed trait QueryValueInstances extends QueryValueInstances1:
23+
implicit final def optionQueryValue[A: QueryValue]: QueryValue[Option[A]] = ???
24+
25+
trait QueryKeyValue[A]
26+
object QueryKeyValue:
27+
implicit def tuple2QueryKeyValue[K: QueryKey, V: QueryValue]: QueryKeyValue[(K, V)] = ???
28+
29+
30+
@main def Test = summon[QueryKeyValue[(String, None.type)]] // error

Diff for: tests/pos/scala-uri.scala

+1-8
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,14 @@ trait QueryValue[-A]
99
object QueryValue extends QueryValueInstances
1010
sealed trait QueryValueInstances1:
1111
implicit final val stringQueryValue: QueryValue[String] = ???
12-
implicit final val noneQueryValue: QueryValue[None.type] = ???
1312

1413
sealed trait QueryValueInstances extends QueryValueInstances1:
1514
implicit final def optionQueryValue[A: QueryValue]: QueryValue[Option[A]] = ???
15+
implicit final val noneQueryValue: QueryValue[None.type] = ???
1616

1717
trait QueryKeyValue[A]
1818
object QueryKeyValue:
1919
implicit def tuple2QueryKeyValue[K: QueryKey, V: QueryValue]: QueryKeyValue[(K, V)] = ???
2020

2121

2222
@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

Diff for: tests/pos/slick-migration-api-example.scala

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
trait Migration
2+
object Migration:
3+
implicit class MigrationConcat[M <: Migration](m: M):
4+
def &[N <: Migration, O](n: N)(implicit ccm: CanConcatMigrations[M, N, O]): O = ???
5+
6+
trait ReversibleMigration extends Migration
7+
trait MigrationSeq extends Migration
8+
trait ReversibleMigrationSeq extends MigrationSeq with ReversibleMigration
9+
10+
trait ToReversible[-A <: Migration]
11+
object ToReversible:
12+
implicit val reversible: ToReversible[ReversibleMigration] = ???
13+
class CanConcatMigrations[-A, -B, +C]
14+
trait CanConcatMigrationsLow:
15+
implicit def default[A <: Migration, B <: Migration]: CanConcatMigrations[A, B, MigrationSeq] = ???
16+
object CanConcatMigrations extends CanConcatMigrationsLow:
17+
implicit def reversible[A <: Migration, B <: Migration](implicit reverseA: ToReversible[A],
18+
reverseB: ToReversible[B]): CanConcatMigrations[A, B, ReversibleMigrationSeq] = ???
19+
20+
@main def Test =
21+
val rm: ReversibleMigration = ???
22+
val rms = rm & rm & rm
23+
summon[rms.type <:< ReversibleMigrationSeq] // error Cannot prove that (rms : slick.migration.api.MigrationSeq) <:< slick.migration.api.ReversibleMigrationSeq.

0 commit comments

Comments
 (0)