From 2facda005615361a701616adccd2d77fb5bea1d1 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Thu, 18 Jul 2024 17:56:04 +0200 Subject: [PATCH 1/4] Fix `Applications#compare#isAsGood#isGiven` to use parameter to apply the logic prioritizing givens over implicits as intended in #19300 Fix #21212 --- .../dotty/tools/dotc/typer/Applications.scala | 4 +-- tests/pos/i13044.scala | 2 +- tests/pos/i21212.scala | 33 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i21212.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 74c20812893b..dc2f02653d16 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1822,7 +1822,7 @@ trait Applications extends Compatibility { } case _ => // (3) def isGiven(alt: TermRef) = - alt1.symbol.is(Given) && alt.symbol != defn.NotGivenClass + alt.symbol.is(Given) && alt.symbol != defn.NotGivenClass def compareValues(tp1: Type, tp2: Type)(using Context) = isAsGoodValueType(tp1, tp2, isGiven(alt1), isGiven(alt2)) tp2 match @@ -1842,7 +1842,7 @@ trait Applications extends Compatibility { * available in 3.0-migration if mode `Mode.OldImplicitResolution` is turned on as well. * It is used to highlight differences between Scala 2 and 3 behavior. * - * - In Scala 3.0-3.5, the behavior is as follows: `T <:p U` iff there is an impliit conversion + * - In Scala 3.0-3.5, the behavior is as follows: `T <:p U` iff there is an implicit conversion * from `T` to `U`, or * * flip(T) <: flip(U) diff --git a/tests/pos/i13044.scala b/tests/pos/i13044.scala index 4c9b8b914062..36299d9e8366 100644 --- a/tests/pos/i13044.scala +++ b/tests/pos/i13044.scala @@ -1,4 +1,4 @@ -//> using options -Xmax-inlines:33 +//> using options -Xmax-inlines:35 import scala.deriving.Mirror import scala.compiletime._ diff --git a/tests/pos/i21212.scala b/tests/pos/i21212.scala new file mode 100644 index 000000000000..2116beb72012 --- /dev/null +++ b/tests/pos/i21212.scala @@ -0,0 +1,33 @@ + +trait Functor[F[_]]: + def map[A, B](fa: F[A])(f: A => B): F[B] = ??? +trait Monad[F[_]] extends Functor[F] +trait MonadError[F[_], E] extends Monad[F]: + def raiseError[A](e: E): F[A] +trait Temporal[F[_]] extends MonadError[F, Throwable] + +trait FunctorOps[F[_], A]: + def map[B](f: A => B): F[B] = ??? +implicit def toFunctorOps[F[_], A](target: F[A])(implicit tc: Functor[F]): FunctorOps[F, A] = ??? + +class ContextBounds[F[_]: Temporal](using err: MonadError[F, Throwable]): + def useCase = err.raiseError(new RuntimeException()) + val bool: F[Boolean] = ??? + def fails = toFunctorOps(bool).map(_ => ()) // warns under -source:3.5, // error under -source:3.6 + +class UsingArguments[F[_]](using Temporal[F])(using err: MonadError[F, Throwable]): + def useCase = err.raiseError(new RuntimeException()) + val bool: F[Boolean] = ??? + def works = toFunctorOps(bool).map(_ => ()) // warns under -source:3.5 + + +object Minimization: + + trait A + trait B extends A + + def test1(using a1: A)(using b1: B) = summon[A] // picks (most general) a1 + def test2(using a2: A)(implicit b2: B) = summon[A] // picks (most general) a2, was ambiguous + def test3(implicit a3: A, b3: B) = summon[A] // picks (most specific) b3 + +end Minimization From 184bdc2f600499d2b9ae44e7018ed24372b5b6f3 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Thu, 18 Jul 2024 18:35:43 +0200 Subject: [PATCH 2/4] Prefer extensions over conversions and implicits for member selection Before the changes, if `isAsGoodValueType` was called with an extension and a given conversion, it would prefer the conversion over the extension, because only the former yielded true in `isGiven`. Which contradicted the logic from searchImplicit which preferred extension over conversions for member selection. --- .../src/dotty/tools/dotc/typer/Applications.scala | 14 ++++++-------- tests/pos/i19715.scala | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index dc2f02653d16..dccd018d72cf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1821,10 +1821,8 @@ trait Applications extends Compatibility { isAsGood(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2) } case _ => // (3) - def isGiven(alt: TermRef) = - alt.symbol.is(Given) && alt.symbol != defn.NotGivenClass def compareValues(tp1: Type, tp2: Type)(using Context) = - isAsGoodValueType(tp1, tp2, isGiven(alt1), isGiven(alt2)) + isAsGoodValueType(tp1, tp2, alt1.symbol.is(Implicit), alt2.symbol.is(Implicit)) tp2 match case tp2: MethodType => true // (3a) case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a) @@ -1861,7 +1859,7 @@ trait Applications extends Compatibility { * for overloading resolution (when `preferGeneral is false), and the opposite relation * `U <: T` or `U convertible to `T` for implicit disambiguation between givens * (when `preferGeneral` is true). For old-style implicit values, the 3.4 behavior is kept. - * If one of the alternatives is a given and the other is an implicit, the given wins. + * If one of the alternatives is an implicit and the other is a given (or an extension), the implicit loses. * * - In Scala 3.5 and Scala 3.6-migration, we issue a warning if the result under * Scala 3.6 differ wrt to the old behavior up to 3.5. @@ -1869,7 +1867,7 @@ trait Applications extends Compatibility { * Also and only for given resolution: If a compared type refers to a given or its module class, use * the intersection of its parent classes instead. */ - def isAsGoodValueType(tp1: Type, tp2: Type, alt1isGiven: Boolean, alt2isGiven: Boolean)(using Context): Boolean = + def isAsGoodValueType(tp1: Type, tp2: Type, alt1IsImplicit: Boolean, alt2IsImplicit: Boolean)(using Context): Boolean = val oldResolution = ctx.mode.is(Mode.OldImplicitResolution) if !preferGeneral || Feature.migrateTo3 && oldResolution then // Normal specificity test for overloading resolution (where `preferGeneral` is false) @@ -1887,7 +1885,7 @@ trait Applications extends Compatibility { if Feature.sourceVersion.isAtMost(SourceVersion.`3.4`) || oldResolution - || !alt1isGiven && !alt2isGiven + || alt1IsImplicit && alt2IsImplicit then // Intermediate rules: better means specialize, but map all type arguments downwards // These are enabled for 3.0-3.5, and for all comparisons between old-style implicits, @@ -1902,8 +1900,8 @@ trait Applications extends Compatibility { case _ => mapOver(t) (flip(tp1p) relaxed_<:< flip(tp2p)) || viewExists(tp1, tp2) else - // New rules: better means generalize, givens always beat implicits - if alt1isGiven != alt2isGiven then alt1isGiven + // New rules: better means generalize, givens (and extensions) always beat implicits + if alt1IsImplicit != alt2IsImplicit then alt2IsImplicit else (tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1) end isAsGoodValueType diff --git a/tests/pos/i19715.scala b/tests/pos/i19715.scala index 91aeda5c1698..be5471ffa9b3 100644 --- a/tests/pos/i19715.scala +++ b/tests/pos/i19715.scala @@ -6,7 +6,8 @@ class NT(t: Tup): object NT: extension (x: NT) def app(n: Int): Boolean = true - given Conversion[NT, Tup] = _.toTup + given c1: Conversion[NT, Tup] = _.toTup + implicit def c2(t: NT): Tup = c1(t) def test = val nt = new NT(Tup()) From e5fb143acf55b2977acdbdeb5f077d329e4c1c4e Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 19 Jul 2024 15:32:28 +0200 Subject: [PATCH 3/4] Make `Namer#ClassCompleter#completerCtx` a given instead of an `implicit val`, to account for the changes in given prioritization from #19300 and #21226, which made it ambiguous with the `Completer#creationContext` given. --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 83964417a6f1..bd0d826bc17d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1106,7 +1106,7 @@ class Namer { typer: Typer => class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) { withDecls(newScope(using ictx)) - protected implicit val completerCtx: Context = localContext(cls) + protected given completerCtx: Context = localContext(cls) private var localCtx: Context = uninitialized From 8f490e1d68c495fd6f765c4a5a6f32e58c715b04 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Tue, 23 Jul 2024 21:11:07 +0200 Subject: [PATCH 4/4] Update PPrint community-project --- community-build/community-projects/PPrint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/PPrint b/community-build/community-projects/PPrint index 2203dc6081f5..34a777f687bc 160000 --- a/community-build/community-projects/PPrint +++ b/community-build/community-projects/PPrint @@ -1 +1 @@ -Subproject commit 2203dc6081f5e8fa89f552b155724b0a8fdcec03 +Subproject commit 34a777f687bc851953e682f99edcae9d2875babc