Skip to content

Commit 46ff151

Browse files
Fix prioritization of givens over implicits (#21226)
- Fix a typo in `Applications#compare#isAsGood#isGiven` which always used `alt1`, to determine if the alternatives passed to `isAsGoodValueType` were givens. - Update `isAsGoodValueType` to not prefer givens over extensions, by negating the `isGiven` parameter, letting extensions and givens now have the same priority level as far as that rule is concerned. - Modify `given`/`implicit` definitions from the `Namer` and the `PPrint` community-project to resolve ambiguity errors introduced by the changes.
2 parents fb983db + 8f490e1 commit 46ff151

File tree

6 files changed

+45
-13
lines changed

6 files changed

+45
-13
lines changed

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

+7-9
Original file line numberDiff line numberDiff line change
@@ -1830,10 +1830,8 @@ trait Applications extends Compatibility {
18301830
isAsGood(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
18311831
}
18321832
case _ => // (3)
1833-
def isGiven(alt: TermRef) =
1834-
alt1.symbol.is(Given) && alt.symbol != defn.NotGivenClass
18351833
def compareValues(tp1: Type, tp2: Type)(using Context) =
1836-
isAsGoodValueType(tp1, tp2, isGiven(alt1), isGiven(alt2))
1834+
isAsGoodValueType(tp1, tp2, alt1.symbol.is(Implicit), alt2.symbol.is(Implicit))
18371835
tp2 match
18381836
case tp2: MethodType => true // (3a)
18391837
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
@@ -1851,7 +1849,7 @@ trait Applications extends Compatibility {
18511849
* available in 3.0-migration if mode `Mode.OldImplicitResolution` is turned on as well.
18521850
* It is used to highlight differences between Scala 2 and 3 behavior.
18531851
*
1854-
* - In Scala 3.0-3.5, the behavior is as follows: `T <:p U` iff there is an impliit conversion
1852+
* - In Scala 3.0-3.5, the behavior is as follows: `T <:p U` iff there is an implicit conversion
18551853
* from `T` to `U`, or
18561854
*
18571855
* flip(T) <: flip(U)
@@ -1870,15 +1868,15 @@ trait Applications extends Compatibility {
18701868
* for overloading resolution (when `preferGeneral is false), and the opposite relation
18711869
* `U <: T` or `U convertible to `T` for implicit disambiguation between givens
18721870
* (when `preferGeneral` is true). For old-style implicit values, the 3.4 behavior is kept.
1873-
* If one of the alternatives is a given and the other is an implicit, the given wins.
1871+
* If one of the alternatives is an implicit and the other is a given (or an extension), the implicit loses.
18741872
*
18751873
* - In Scala 3.5 and Scala 3.6-migration, we issue a warning if the result under
18761874
* Scala 3.6 differ wrt to the old behavior up to 3.5.
18771875
*
18781876
* Also and only for given resolution: If a compared type refers to a given or its module class, use
18791877
* the intersection of its parent classes instead.
18801878
*/
1881-
def isAsGoodValueType(tp1: Type, tp2: Type, alt1isGiven: Boolean, alt2isGiven: Boolean)(using Context): Boolean =
1879+
def isAsGoodValueType(tp1: Type, tp2: Type, alt1IsImplicit: Boolean, alt2IsImplicit: Boolean)(using Context): Boolean =
18821880
val oldResolution = ctx.mode.is(Mode.OldImplicitResolution)
18831881
if !preferGeneral || Feature.migrateTo3 && oldResolution then
18841882
// Normal specificity test for overloading resolution (where `preferGeneral` is false)
@@ -1896,7 +1894,7 @@ trait Applications extends Compatibility {
18961894

18971895
if Feature.sourceVersion.isAtMost(SourceVersion.`3.4`)
18981896
|| oldResolution
1899-
|| !alt1isGiven && !alt2isGiven
1897+
|| alt1IsImplicit && alt2IsImplicit
19001898
then
19011899
// Intermediate rules: better means specialize, but map all type arguments downwards
19021900
// These are enabled for 3.0-3.5, and for all comparisons between old-style implicits,
@@ -1911,8 +1909,8 @@ trait Applications extends Compatibility {
19111909
case _ => mapOver(t)
19121910
(flip(tp1p) relaxed_<:< flip(tp2p)) || viewExists(tp1, tp2)
19131911
else
1914-
// New rules: better means generalize, givens always beat implicits
1915-
if alt1isGiven != alt2isGiven then alt1isGiven
1912+
// New rules: better means generalize, givens (and extensions) always beat implicits
1913+
if alt1IsImplicit != alt2IsImplicit then alt2IsImplicit
19161914
else (tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
19171915
end isAsGoodValueType
19181916

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ class Namer { typer: Typer =>
11061106
class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) {
11071107
withDecls(newScope(using ictx))
11081108

1109-
protected implicit val completerCtx: Context = localContext(cls)
1109+
protected given completerCtx: Context = localContext(cls)
11101110

11111111
private var localCtx: Context = uninitialized
11121112

tests/pos/i13044.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//> using options -Xmax-inlines:33
1+
//> using options -Xmax-inlines:35
22

33
import scala.deriving.Mirror
44
import scala.compiletime._

tests/pos/i19715.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ class NT(t: Tup):
66
object NT:
77
extension (x: NT)
88
def app(n: Int): Boolean = true
9-
given Conversion[NT, Tup] = _.toTup
9+
given c1: Conversion[NT, Tup] = _.toTup
10+
implicit def c2(t: NT): Tup = c1(t)
1011

1112
def test =
1213
val nt = new NT(Tup())

tests/pos/i21212.scala

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
trait Functor[F[_]]:
3+
def map[A, B](fa: F[A])(f: A => B): F[B] = ???
4+
trait Monad[F[_]] extends Functor[F]
5+
trait MonadError[F[_], E] extends Monad[F]:
6+
def raiseError[A](e: E): F[A]
7+
trait Temporal[F[_]] extends MonadError[F, Throwable]
8+
9+
trait FunctorOps[F[_], A]:
10+
def map[B](f: A => B): F[B] = ???
11+
implicit def toFunctorOps[F[_], A](target: F[A])(implicit tc: Functor[F]): FunctorOps[F, A] = ???
12+
13+
class ContextBounds[F[_]: Temporal](using err: MonadError[F, Throwable]):
14+
def useCase = err.raiseError(new RuntimeException())
15+
val bool: F[Boolean] = ???
16+
def fails = toFunctorOps(bool).map(_ => ()) // warns under -source:3.5, // error under -source:3.6
17+
18+
class UsingArguments[F[_]](using Temporal[F])(using err: MonadError[F, Throwable]):
19+
def useCase = err.raiseError(new RuntimeException())
20+
val bool: F[Boolean] = ???
21+
def works = toFunctorOps(bool).map(_ => ()) // warns under -source:3.5
22+
23+
24+
object Minimization:
25+
26+
trait A
27+
trait B extends A
28+
29+
def test1(using a1: A)(using b1: B) = summon[A] // picks (most general) a1
30+
def test2(using a2: A)(implicit b2: B) = summon[A] // picks (most general) a2, was ambiguous
31+
def test3(implicit a3: A, b3: B) = summon[A] // picks (most specific) b3
32+
33+
end Minimization

0 commit comments

Comments
 (0)