diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 27df2dfac103..481f9f1b68a9 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,7 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter - val debug = noPrinter // no type annotion here to force inlining + val debug = noPrinter // no type annotation here to force inlining val derive: Printer = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index efee841c3a34..5202666b5414 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -70,6 +70,7 @@ class ScalaSettings extends Settings.SettingGroup { val XprintTypes: Setting[Boolean] = BooleanSetting("-Xprint-types", "Print tree types (debugging option).") val XprintDiff: Setting[Boolean] = BooleanSetting("-Xprint-diff", "Print changed parts of the tree since last print.") val XprintDiffDel: Setting[Boolean] = BooleanSetting("-Xprint-diff-del", "Print changed parts of the tree since last print including deleted parts.") + val XprintInline: Setting[Boolean] = BooleanSetting("-Xprint-inline", "Show where inlined code comes from") val Xprompt: Setting[Boolean] = BooleanSetting("-Xprompt", "Display a prompt after each error (debugging option).") val XmainClass: Setting[String] = StringSetting("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d )", "") val XnoValueClasses: Setting[Boolean] = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.") @@ -157,7 +158,6 @@ class ScalaSettings extends Settings.SettingGroup { val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings: Setting[Boolean] = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val YshowVarBounds: Setting[Boolean] = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds") - val YshowNoInline: Setting[Boolean] = BooleanSetting("-Yshow-no-inline", "Show inlined code without the 'inlined from' info") val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ac6905ff05ae..5f5913d84f7e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1558,7 +1558,7 @@ object SymDenotations { case p :: parents1 => p.classSymbol match { case pcls: ClassSymbol => builder.addAll(pcls.baseClasses) - case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") + case _ => assert(isRefinementClass || p.isError || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") } traverse(parents1) case nil => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d43ab305932b..f320e0053c01 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -417,7 +417,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => (("/* inlined from " ~ (if (call.isEmpty) "outside" else toText(call)) ~ " */ ") `provided` - !homogenizedView && !ctx.settings.YshowNoInline.value) ~ + !homogenizedView && ctx.settings.XprintInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ecdce2384336..4b4520b0e7e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1344,9 +1344,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic => (flip(tp1) relaxed_<:< flip(tp2)) || viewExists(tp1, tp2) } - // # skipped implicit parameters in tp1 - # skipped implicit parameters in tp2 - var implicitBalance: Int = 0 - /** Widen the result type of synthetic implied methods from the implementation class to the * type that's implemented. Example * @@ -1381,12 +1378,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } /** Drop any implicit parameter section */ - def stripImplicit(tp: Type, weight: Int): Type = tp match { + def stripImplicit(tp: Type): Type = tp match { case mt: MethodType if mt.isImplicitMethod => - implicitBalance += mt.paramInfos.length * weight - resultTypeApprox(mt) + stripImplicit(resultTypeApprox(mt)) case pt: PolyType => - pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight)) + pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType)) case _ => tp } @@ -1412,18 +1408,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val fullType1 = widenImplied(alt1.widen, alt1) val fullType2 = widenImplied(alt2.widen, alt2) - val strippedType1 = stripImplicit(fullType1, -1) - val strippedType2 = stripImplicit(fullType2, +1) + val strippedType1 = stripImplicit(fullType1) + val strippedType2 = stripImplicit(fullType2) val result = compareWithTypes(strippedType1, strippedType2) - if (result != 0 || !ctx.typerState.test(implicit ctx => strippedType1 =:= strippedType2)) - result - else if (implicitBalance != 0) - -implicitBalance.signum - else if ((strippedType1 `ne` fullType1) || (strippedType2 `ne` fullType2)) - compareWithTypes(fullType1, fullType2) - else - 0 + if (result != 0) result + else if (strippedType1 eq fullType1) + if (strippedType2 eq fullType2) 0 // no implicits either side: its' a draw + else 1 // prefer 1st alternative with no implicits + else if (strippedType2 eq fullType2) -1 // prefer 2nd alternative with no implicits + else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters }} def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index df2e295598f7..71cce7e08c22 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2547,7 +2547,10 @@ class Typer extends Namer if (arg.tpe.isError) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil } tryEither { implicit ctx => - typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt, locked) + val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs) + if (wtp.isContextual) app.pushAttachment(untpd.ApplyGiven, ()) + typr.println(i"try with default implicit args $app") + typed(app, pt, locked) } { (_, _) => issueErrors() } diff --git a/docs/docs/reference/changed-features/implicit-resolution.md b/docs/docs/reference/changed-features/implicit-resolution.md index e905ed64de00..c68686290527 100644 --- a/docs/docs/reference/changed-features/implicit-resolution.md +++ b/docs/docs/reference/changed-features/implicit-resolution.md @@ -114,11 +114,9 @@ affect implicits on the language level. An alternative A is _more specific_ than an alternative B if - the relative weight of A over B is greater than the relative weight of B over A, or - - the relative weights are the same, and the returned types of A and B are - unifiable, and A takes more inferable parameters than B, or - - the relative weights and the number of inferable parameters are the same, and - the returned types of A and B are unifiable, and - A is more specific than B if all inferable parameters in either alternative are + - the relative weights are the same, and A takes no implicit parameters but B does, or + - the relative weights are the same, both A and B take implicit parameters, and + A is more specific than B if all implicit parameters in either alternative are replaced by regular parameters. [//]: # todo: expand with precise rules diff --git a/tests/pos/i5978.scala b/tests/pos/i5978.scala index a17da36bcf37..705c2a226346 100644 --- a/tests/pos/i5978.scala +++ b/tests/pos/i5978.scala @@ -63,14 +63,18 @@ package p3 { import implied TextParser._ import TextParser._ - val tp_v: TokenParser[Char, Position[CharSequence]] = TextParser.TP - val tp_i = the[TokenParser[Char, Position[CharSequence]]] - implicit val co_i: Conversion[Char, Position[CharSequence]] = the[Conversion[Char, Position[CharSequence]]] - val co_x : Position[CharSequence] = 'x' + val co_i: Conversion[Char, Position[CharSequence]] = the[Conversion[Char, Position[CharSequence]]] { - implied XXX for Conversion[Char, Position[CharSequence]] = co_i - val co_y : Position[CharSequence] = 'x' + val tp_v: TokenParser[Char, Position[CharSequence]] = TextParser.TP + val tp_i = the[TokenParser[Char, Position[CharSequence]]] + implied for Conversion[Char, Position[CharSequence]] = co_i + val co_x : Position[CharSequence] = 'x' + + { + implied XXX for Conversion[Char, Position[CharSequence]] = co_i + val co_y : Position[CharSequence] = 'x' + } } } } diff --git a/tests/run/implicit-functors.check b/tests/run/implicit-functors.check new file mode 100644 index 000000000000..d042012a9681 --- /dev/null +++ b/tests/run/implicit-functors.check @@ -0,0 +1,2 @@ +functorId +funcorConst diff --git a/tests/run/implicit-functors.scala b/tests/run/implicit-functors.scala new file mode 100644 index 000000000000..d3570c7f2c72 --- /dev/null +++ b/tests/run/implicit-functors.scala @@ -0,0 +1,25 @@ +object Utils { + type Id[t] = t + type Const[c] = [t] => c +} + +import Utils._ + +class Instances[F[_[_]], T[_]] + +trait Functor[F[_]] + +object Functor { + implicit val functorId: Functor[Id] = { println("functorId"); null } + implicit def functorGen[F[_]](implicit inst: Instances[Functor, F]): Functor[F] = { println("funcorGen"); null } + implicit def functorConst[T]: Functor[Const[T]] = { println("funcorConst"); null } +} + +case class Sm[A](value: A) +object Sm { + implicit def smInstances[F[_[_]]]: Instances[F, Sm] = { println("smInstances"); null } +} + +object Test extends App { + implicitly[Functor[Sm]] +} \ No newline at end of file diff --git a/tests/run/implicit-specifity.scala b/tests/run/implicit-specifity.scala index 425359ba02b5..d21462d74f77 100644 --- a/tests/run/implicit-specifity.scala +++ b/tests/run/implicit-specifity.scala @@ -12,19 +12,27 @@ object Generic { implied showGen[T] given Generic for Show[T] = new Show[T](2) } +class Generic2 +object Generic2 { + opaque type HiPriority = AnyRef + implied showGen[T] for (Show[T] & HiPriority) = new Show[T](2).asInstanceOf +} + +class SubGen extends Generic +object SubGen { + implied for SubGen +} object Contextual { trait Context implied ctx for Context implied showGen[T] given Generic for Show[T] = new Show[T](2) implied showGen[T] given Generic, Context for Show[T] = new Show[T](3) + implied showGen[T] given SubGen for Show[T] = new Show[T](4) } object Test extends App { assert(Show[Int] == 0) assert(Show[String] == 1) - assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list - - { import implied Contextual._ - assert(Show[Generic] == 3) - } + assert(Show[Generic] == 1) // showGen loses against fallback due to longer argument list + assert(Show[Generic2] == 2) // ... but the opaque type intersection trick works. } diff --git a/tests/run/implied-divergence.scala b/tests/run/implied-divergence.scala new file mode 100644 index 000000000000..739791581176 --- /dev/null +++ b/tests/run/implied-divergence.scala @@ -0,0 +1,13 @@ +// Checks that divergence checking works before going into +// recursions. +case class E(x: E | Null) + +implied e for E(null) + +object Test extends App { + + implied f given (e: E) for E(e) + + assert(the[E].toString == "E(E(null))") + +} \ No newline at end of file diff --git a/tests/run/implied-priority.scala b/tests/run/implied-priority.scala new file mode 100644 index 000000000000..cdd89d78631a --- /dev/null +++ b/tests/run/implied-priority.scala @@ -0,0 +1,161 @@ +/* These tests show various mechanisms available for implicit prioritization. + */ + +class E[T](val str: String) // The type for which we infer terms below + +class Arg[T] // An argument that we use as a given for some implied instances below + +/* First, two schemes that require a pre-planned architecture for how and + * where implied instances are defined. + * + * Traditional scheme: prioritize with location in class hierarchy + */ +class LowPriorityImplicits { + implied t1[T] for E[T]("low") +} + +object NormalImplicits extends LowPriorityImplicits { + implied t2[T] given Arg[T] for E[T]("norm") +} + +def test1 = { + import implied NormalImplicits._ + assert(the[E[String]].str == "low") // No Arg available, so only t1 applies + + { implied for Arg[String] + assert(the[E[String]].str == "norm") // Arg available, t2 takes priority + } +} + +/* New scheme: dummy implicit arguments that indicate priorities + */ +object Priority { + class Low + object Low { implied for Low } + + class High extends Low + object High { implied for High } +} + +object Impl2 { + implied t1[T] given Priority.Low for E[T]("low") + implied t2[T] given Priority.High given Arg[T] for E[T]("norm") +} + +def test2 = { + import implied Impl2._ + assert(the[E[String]].str == "low") // No Arg available, so only t1 applies + + { implied for Arg[String] + assert(the[E[String]].str == "norm") // Arg available, t2 takes priority + } +} + +/* The remaining tests show how we can add an override of highest priority or + * a fallback of lowest priority to a group of existing implied instances, without + * needing to change the location or definition of those instances. + * + * First, consider the problem how to define an override of highest priority. + * If all of the alternatives in the existing hierarchy take implicit arguments, + * an alternative without implicit arguments would override all of them. + */ +object Impl2a { + implied t3[T] for E[T]("hi") +} + +def test2a = { + import implied Impl2._ + import implied Impl2a._ + + implied for Arg[String] + assert(the[E[String]].str == "hi") +} + +/* If that solution is not applicable, we can define an override by refining the + * result type of the implied instance, e.g. like this: + */ +object Impl3 { + implied t1[T] for E[T]("low") +} + +object Override { + trait HighestPriority // A marker trait to indicate a higher priority + + implied over[T] for E[T]("hi"), HighestPriority +} + +def test3 = { + import implied Impl3._ + assert(the[E[String]].str == "low") // only t1 is available + + { import implied Override._ + import implied Impl3._ + assert(the[E[String]].str == "hi") // `over` takes priority since its result type is a subtype of t1's. + } +} + +/* Now consider the dual problem: How to install a fallback with lower priority than existing + * implied instances that kicks in when none of the other instances are applicable. + * We get there in two stages. The first stage is by defining an explicit `withFallback` method + * that takes the right implicit and returns it. This can be achieved using an implicit parameter + * with a default argument. + */ +object Impl4 { + implied t1 for E[String]("string") + implied t2[T] given Arg[T] for E[T]("generic") +} + +object fallback4 { + def withFallback[T] given (ev: E[T] = new E[T]("fallback")): E[T] = ev +} + +def test4 = { + import implied Impl4._ + import fallback4._ + assert(withFallback[String].str == "string") // t1 is applicable + assert(withFallback[Int].str == "fallback") // No applicable instances, pick the default + + { implied for Arg[Int] + assert(withFallback[Int].str == "generic") // t2 is applicable + } +} + +/* The final setup considers the problem how to define a fallback with lower priority than existing + * implicits that exists as an implicit instance alongside the others. This can be achieved + * by combining the implicit parameter with default technique for getting an existing impplicit + * or a fallback with the result refinement technique for overriding all existing implicit instances. + * + * It employs a more re-usable version of the result refinement trick. + */ +opaque type HigherPriority = Any +object HigherPriority { + def inject[T](x: T): T & HigherPriority = x +} + +object fallback5 { + implied [T] given (ev: E[T] = new E[T]("fallback")) for (E[T] & HigherPriority) = HigherPriority.inject(ev) +} + +def test5 = { + import implied Impl4._ + import implied fallback5._ + + // All inferred terms go through the implied instance in fallback5. + // They differ in what implicit argument is synthesized for that instance. + assert(the[E[String]].str == "string") // t1 is applicable + assert(the[E[Int]].str == "fallback") // No applicable instances, pick the default + + { implied for Arg[Int] + assert(the[E[Int]].str == "generic") // t2 is applicable + } +} + +object Test extends App { + test1 + test2 + test2a + test3 + test4 + test5 +} + diff --git a/tests/run/implied-specifity-2.scala b/tests/run/implied-specifity-2.scala index 117141d50a50..8818eb56c2fd 100644 --- a/tests/run/implied-specifity-2.scala +++ b/tests/run/implied-specifity-2.scala @@ -29,8 +29,32 @@ object Baz { implied baz given High for Foo[Bar[Baz]](5) } +class Arg +implied for Arg + +class Bam(val str: String) +implied lo given Low for Bam("lo") +implied hi given High given Arg for Bam("hi") + +class Bam2(val str: String) +implied lo2 given Low for Bam2("lo") +implied mid2 given High given Arg for Bam2("mid") +implied hi2 for Bam2("hi") + +class Arg2 +class Red(val str: String) +implied normal given Arg2 for Red("normal") +implied reduced given (ev: Arg2 | Low) for Red("reduced") + object Test extends App { assert(Foo[Int] == 0) assert(Foo[Bar[Int]] == 3) assert(Foo[Bar[Baz]] == 5) + assert(the[Bam].str == "hi") + assert(the[Bam2].str == "hi") + assert(the[Red].str == "reduced") + + { implied for Arg2 + assert(the[Red].str == "normal") + } } \ No newline at end of file diff --git a/tests/run/implied-specifity.scala b/tests/run/implied-specifity.scala deleted file mode 100644 index 4d0b72916183..000000000000 --- a/tests/run/implied-specifity.scala +++ /dev/null @@ -1,30 +0,0 @@ -case class Show[T](val i: Int) -object Show { - def apply[T](implicit st: Show[T]): Int = st.i - - implied showInt for Show[Int](0) - implied fallback[T] for Show[T](1) -} - -class Generic -object Generic { - implied gen for Generic - implied showGen[T] given Generic for Show[T](2) -} - -object Contextual { - trait Context - implied ctx for Context - implied showGen2[T] given Generic for Show[T](2) - implied showGen3[T] given Generic, Context for Show[T](3) -} - -object Test extends App { - assert(Show[Int] == 0) - assert(Show[String] == 1) - assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list - - { import implied Contextual._ - assert(Show[Generic] == 3) - } -} diff --git a/tests/run/overloading-specifity.scala b/tests/run/overloading-specifity.scala index 79746a3c1552..8f84fcb333de 100644 --- a/tests/run/overloading-specifity.scala +++ b/tests/run/overloading-specifity.scala @@ -1,5 +1,5 @@ // Shows that now implicit parameters act as a tie-breaker. -// The alternative with more implicit parameters wins. +// The alternative with fewer implicit parameters wins. case class Show[T](val i: Int) class Generic @@ -12,16 +12,16 @@ object Test extends App { trait Context implied ctx for Context - object a { - def foo[T](implicit gen: Generic): Show[T] = new Show[T](1) - def foo[T](implicit gen: Generic, ctx: Context): Show[T] = new Show[T](2) - } object b { def foo[T](implicit gen: Generic): Show[T] = new Show[T](1) def foo[T]: Show[T] = new Show[T](2) } - assert(a.foo[Int].i == 2) - assert(b.foo[Int].i == 1) + assert(b.foo[Int].i == 2) + + def f: Int = 1 + def f(x: Int): Int = x + val x = f + val x1: Int = x } \ No newline at end of file