From c178af58e4fbb7fe9d90f2b07dc12bbd625fcc20 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 11 Mar 2019 10:36:30 +0100 Subject: [PATCH 01/13] Change implicit resolution rule wrt implicit parameters The method with fewer parameters wins now over methods with more parameters. The reason for the change is i3430.scala, with Nil.sum Here, orderings for any type are eligible implied instances for `sum`'s implicit `Ordering` parameter. If we prioritize arguments with implicit parameters, we have to try all tuple orderings which themselves take as many implicit parameters as the tuple has elements. This leads to an infinite recursion with very broad fanout, so the observed effect is that the compiler hangs. By prioritizing shorter implicit parameter lists, we avoid this problem. --- .../dotty/tools/dotc/typer/Applications.scala | 4 +-- .../changed-features/implicit-resolution.md | 4 +-- tests/run/implicit-specifity.scala | 16 ++++++++-- tests/run/implied-specifity.scala | 30 ------------------- tests/run/overloading-specifity.scala | 6 ++-- 5 files changed, 20 insertions(+), 40 deletions(-) delete mode 100644 tests/run/implied-specifity.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ecdce2384336..f32e37b2db1a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1416,10 +1416,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val strippedType2 = stripImplicit(fullType2, +1) val result = compareWithTypes(strippedType1, strippedType2) - if (result != 0 || !ctx.typerState.test(implicit ctx => strippedType1 =:= strippedType2)) + if (result != 0) result else if (implicitBalance != 0) - -implicitBalance.signum + implicitBalance.signum else if ((strippedType1 `ne` fullType1) || (strippedType2 `ne` fullType2)) compareWithTypes(fullType1, fullType2) else diff --git a/docs/docs/reference/changed-features/implicit-resolution.md b/docs/docs/reference/changed-features/implicit-resolution.md index e905ed64de00..fbef587ea4d6 100644 --- a/docs/docs/reference/changed-features/implicit-resolution.md +++ b/docs/docs/reference/changed-features/implicit-resolution.md @@ -114,10 +114,8 @@ 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 are the same, and A takes fewer 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 replaced by regular parameters. diff --git a/tests/run/implicit-specifity.scala b/tests/run/implicit-specifity.scala index 425359ba02b5..5402c56423d7 100644 --- a/tests/run/implicit-specifity.scala +++ b/tests/run/implicit-specifity.scala @@ -12,19 +12,31 @@ 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 + assert(Show[Generic] == 1) // showGen loses against fallback due to longer argument list + assert(Show[Generic2] == 2) // ... but the opaque type intersection trick works. { import implied Contextual._ - assert(Show[Generic] == 3) + assert(Show[Generic] == 4) // shorter, more specific implicit parameter list wins } } 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..73e450d65ef7 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 @@ -21,7 +21,7 @@ object Test extends App { def foo[T]: Show[T] = new Show[T](2) } - assert(a.foo[Int].i == 2) - assert(b.foo[Int].i == 1) + assert(a.foo[Int].i == 1) + assert(b.foo[Int].i == 2) } \ No newline at end of file From b47d2f25498e9e3ffcc23b93e12b427a72ec7d5b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Mar 2019 19:13:44 +0100 Subject: [PATCH 02/13] Add test to show that rule is in line with normal overloading resolution --- tests/run/overloading-specifity.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/run/overloading-specifity.scala b/tests/run/overloading-specifity.scala index 73e450d65ef7..71c5df5d26dc 100644 --- a/tests/run/overloading-specifity.scala +++ b/tests/run/overloading-specifity.scala @@ -24,4 +24,9 @@ object Test extends App { assert(a.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 From 8b8d8138568732c13cd99b5296fd57b09f49babf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 Mar 2019 23:32:54 +0100 Subject: [PATCH 03/13] Another tweak to overloading resolution After thinking even more about it, I now believe we should revise the rule. The idea of using implicits as a priority mechanism is so that we can write something like this: ``` class LowPrio class MidPrio extends LowPrio class HighPrio extends MidPrio implied for LowPrio implied for MidPrio implied for HighPrio implied fallback given LowPrio for A implied standard given MidPrio for A implied specialized given HighPrio for A ``` (usually in different modules, of course). This is a good alternative to the previous priorization by owner subclassing since it places no demands on where implied instances are put and is stable under aliasing/forwarding. But to do this reliably it should also work if some of the alternatives take additional implicit parameters. E.g. ``` implied standard given MidPrio given B for A ``` The "more implicit parameters is better" rule would make standard of higher priority than specialized, which defeats the purpose. --- .../dotty/tools/dotc/typer/Applications.scala | 18 ++++++------------ .../changed-features/implicit-resolution.md | 6 +++--- tests/run/implicit-specifity.scala | 4 ---- tests/run/overloading-specifity.scala | 5 ----- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index f32e37b2db1a..68dcfa908a7a 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 * @@ -1383,7 +1380,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Drop any implicit parameter section */ def stripImplicit(tp: Type, weight: Int): Type = tp match { case mt: MethodType if mt.isImplicitMethod => - implicitBalance += mt.paramInfos.length * weight resultTypeApprox(mt) case pt: PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight)) @@ -1416,14 +1412,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val strippedType2 = stripImplicit(fullType2, +1) val result = compareWithTypes(strippedType1, strippedType2) - if (result != 0) - 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/docs/docs/reference/changed-features/implicit-resolution.md b/docs/docs/reference/changed-features/implicit-resolution.md index fbef587ea4d6..c68686290527 100644 --- a/docs/docs/reference/changed-features/implicit-resolution.md +++ b/docs/docs/reference/changed-features/implicit-resolution.md @@ -114,9 +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 A takes fewer inferable parameters than B, or - - the relative weights and the number of inferable parameters are the same, 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/run/implicit-specifity.scala b/tests/run/implicit-specifity.scala index 5402c56423d7..d21462d74f77 100644 --- a/tests/run/implicit-specifity.scala +++ b/tests/run/implicit-specifity.scala @@ -35,8 +35,4 @@ object Test extends App { assert(Show[String] == 1) assert(Show[Generic] == 1) // showGen loses against fallback due to longer argument list assert(Show[Generic2] == 2) // ... but the opaque type intersection trick works. - - { import implied Contextual._ - assert(Show[Generic] == 4) // shorter, more specific implicit parameter list wins - } } diff --git a/tests/run/overloading-specifity.scala b/tests/run/overloading-specifity.scala index 71c5df5d26dc..8f84fcb333de 100644 --- a/tests/run/overloading-specifity.scala +++ b/tests/run/overloading-specifity.scala @@ -12,16 +12,11 @@ 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 == 1) assert(b.foo[Int].i == 2) def f: Int = 1 From cd9a29f926bf1887bea4a279f2bc821ae554022f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Mar 2019 17:02:38 +0100 Subject: [PATCH 04/13] Support multiple implicit parameter lists --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 10 +++++----- tests/run/implied-specifity-2.scala | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 68dcfa908a7a..4b4520b0e7e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1378,11 +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 => - 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 } @@ -1408,8 +1408,8 @@ 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) result diff --git a/tests/run/implied-specifity-2.scala b/tests/run/implied-specifity-2.scala index 117141d50a50..705c304c18b1 100644 --- a/tests/run/implied-specifity-2.scala +++ b/tests/run/implied-specifity-2.scala @@ -29,8 +29,16 @@ 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") + object Test extends App { assert(Foo[Int] == 0) assert(Foo[Bar[Int]] == 3) assert(Foo[Bar[Baz]] == 5) + assert(the[Bam].str == "hi") } \ No newline at end of file From 56074caeab784664dc7a861d552fc32739ce3473 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Mar 2019 17:26:52 +0100 Subject: [PATCH 05/13] Fix test The test previously compiled by accident due to the "more implicit parameters win" rule. Once that rule got changed it should have failed, but it seems the error (illegal forward reference) was not detected. Rebasing this commit on latest master detects the error: ``` implicit val co_i: Conversion[Char, Position[CharSequence]] = the[Conversion[Char, Position[CharSequence]]] ^ co_i is a forward reference extending over the definition of co_i ``` The fix to the test avoids the error again. --- tests/pos/i5978.scala | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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' + } } } } From f307da22d4dfe1bae306d3176f727d47431a5d9a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Mar 2019 17:42:33 +0100 Subject: [PATCH 06/13] Add test cases --- tests/run/implied-specifity-2.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/run/implied-specifity-2.scala b/tests/run/implied-specifity-2.scala index 705c304c18b1..89599d1d1cfb 100644 --- a/tests/run/implied-specifity-2.scala +++ b/tests/run/implied-specifity-2.scala @@ -36,9 +36,20 @@ 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 Red(val str: String) +implied normal given Arg for Red("normal") +implied reduced given Arg given 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 == "normal") } \ No newline at end of file From 55c759e935c527de9720bdbb730a1d283cf7ac5c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Mar 2019 20:29:04 +0100 Subject: [PATCH 07/13] Test a fallback scheme --- tests/run/implied-specifity-2.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/run/implied-specifity-2.scala b/tests/run/implied-specifity-2.scala index 89599d1d1cfb..8818eb56c2fd 100644 --- a/tests/run/implied-specifity-2.scala +++ b/tests/run/implied-specifity-2.scala @@ -41,9 +41,10 @@ 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 Arg for Red("normal") -implied reduced given Arg given Low for Red("reduced") +implied normal given Arg2 for Red("normal") +implied reduced given (ev: Arg2 | Low) for Red("reduced") object Test extends App { assert(Foo[Int] == 0) @@ -51,5 +52,9 @@ object Test extends App { assert(Foo[Bar[Baz]] == 5) assert(the[Bam].str == "hi") assert(the[Bam2].str == "hi") - //assert(the[Red].str == "normal") + assert(the[Red].str == "reduced") + + { implied for Arg2 + assert(the[Red].str == "normal") + } } \ No newline at end of file From 210aa64e34163ace8fd7c69a454d0205218c7c6f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Mar 2019 13:54:24 +0100 Subject: [PATCH 08/13] Replace -Yshow-no-inline by -Xprint-inline Inline traces are bulky, it's best not to show them by default --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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 => "" From b18f417b446eeb66387c0e4bc5a795bcfdb49e99 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Mar 2019 14:01:16 +0100 Subject: [PATCH 09/13] Survive error types in base types --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 => From 555cafa34aa28776ed28866a14ae360406872aea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Mar 2019 16:25:06 +0100 Subject: [PATCH 10/13] Fix default arguments for inferable parameters Default arguments must be passed as `given` arguments to match. Without this change we get a stackoverflow in implied-priority.scala. --- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +- tests/run/implied-priority.scala | 114 ++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/run/implied-priority.scala 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/tests/run/implied-priority.scala b/tests/run/implied-priority.scala new file mode 100644 index 000000000000..059df58a27ae --- /dev/null +++ b/tests/run/implied-priority.scala @@ -0,0 +1,114 @@ +class Arg[T] + +// Traditional scheme: use location in class hierarchy + +class E[T](val str: String) + +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") + + { implied for Arg[String] + assert(the[E[String]].str == "norm") + } +} + +// Priority arguments: + +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") + + { implied for Arg[String] + assert(the[E[String]].str == "norm") + } +} + +// Adding an override to an existing hierarchy: +// 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 not, we can use result refinement: + +object Impl3 { + implied t1[T] for E[T]("low") +} + +object Override { + trait HighestPriority + + implied over[T] for E[T]("hi"), HighestPriority +} + +def test3 = { + import implied Impl3._ + assert(the[E[String]].str == "low") + + { import implied Override._ + assert(the[E[String]].str == "hi") + } +} + +// Adding a fallback to an existing hierarchy: +object Impl4 { + implied t1 for E[String]("string") + implied t2[T] given Arg[T] for E[T]("generic") +} + +object fb { + def withFallback[T] given (ev: E[T] = new E[T]("fallback")): E[T] = ev + implied [T] given (ev: E[T] = new E[T]("fallback")) for E[T] = ev +} + +def test4 = { + import implied Impl4._ + import fb._ + assert(withFallback[String].str == "string") + assert(withFallback[Int].str == "fallback") + + { implied for Arg[Int] + assert(withFallback[Int].str == "generic") + } +} + + +object Test extends App { + test1 + test2 + test2a + test3 + test4 +} + From 005ad5014aa79f30816a6edac828a77dd0fe0ced Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Mar 2019 16:46:50 +0100 Subject: [PATCH 11/13] Test for divergence checking --- compiler/src/dotty/tools/dotc/config/Printers.scala | 2 +- tests/run/implied-divergence.scala | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/run/implied-divergence.scala 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/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 From 0d1d9637f981f6358e7e69e8b670ad458cf94aa5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Mar 2019 17:38:26 +0100 Subject: [PATCH 12/13] Flesh out and document priority test --- tests/run/implied-priority.scala | 97 ++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/tests/run/implied-priority.scala b/tests/run/implied-priority.scala index 059df58a27ae..cdd89d78631a 100644 --- a/tests/run/implied-priority.scala +++ b/tests/run/implied-priority.scala @@ -1,9 +1,15 @@ -class Arg[T] +/* These tests show various mechanisms available for implicit prioritization. + */ -// Traditional scheme: use location in class hierarchy +class E[T](val str: String) // The type for which we infer terms below -class E[T](val str: String) +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") } @@ -14,18 +20,19 @@ object NormalImplicits extends LowPriorityImplicits { def test1 = { import implied NormalImplicits._ - assert(the[E[String]].str == "low") + assert(the[E[String]].str == "low") // No Arg available, so only t1 applies { implied for Arg[String] - assert(the[E[String]].str == "norm") + assert(the[E[String]].str == "norm") // Arg available, t2 takes priority } } -// Priority arguments: - +/* 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 } } @@ -37,17 +44,21 @@ object Impl2 { def test2 = { import implied Impl2._ - assert(the[E[String]].str == "low") + assert(the[E[String]].str == "low") // No Arg available, so only t1 applies { implied for Arg[String] - assert(the[E[String]].str == "norm") + assert(the[E[String]].str == "norm") // Arg available, t2 takes priority } } -// Adding an override to an existing hierarchy: -// If all of the alternatives in the existing hierarchy take implicit arguments, -// an alternative without implicit arguments would override all of them. - +/* 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") } @@ -60,49 +71,84 @@ def test2a = { assert(the[E[String]].str == "hi") } -// If not, we can use result refinement: - +/* 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 + 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") + assert(the[E[String]].str == "low") // only t1 is available { import implied Override._ - assert(the[E[String]].str == "hi") + import implied Impl3._ + assert(the[E[String]].str == "hi") // `over` takes priority since its result type is a subtype of t1's. } } -// Adding a fallback to an existing hierarchy: +/* 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 fb { +object fallback4 { def withFallback[T] given (ev: E[T] = new E[T]("fallback")): E[T] = ev - implied [T] given (ev: E[T] = new E[T]("fallback")) for E[T] = ev } def test4 = { import implied Impl4._ - import fb._ - assert(withFallback[String].str == "string") - assert(withFallback[Int].str == "fallback") + 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") + 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 @@ -110,5 +156,6 @@ object Test extends App { test2a test3 test4 + test5 } From 31ac2ac6a0f24551c7a83d503aa1b729957a6368 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Mar 2019 18:51:18 +0100 Subject: [PATCH 13/13] Add test --- tests/run/implicit-functors.check | 2 ++ tests/run/implicit-functors.scala | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/run/implicit-functors.check create mode 100644 tests/run/implicit-functors.scala 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