Skip to content

Commit e4c4e0a

Browse files
authored
Merge pull request #6071 from dotty-staging/fix-impl-res2
Change implicit resolution rule wrt implicit parameters
2 parents 1762f23 + 31ac2ac commit e4c4e0a

16 files changed

+277
-75
lines changed

compiler/src/dotty/tools/dotc/config/Printers.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ object Printers {
1717
val checks: Printer = noPrinter
1818
val config: Printer = noPrinter
1919
val cyclicErrors: Printer = noPrinter
20-
val debug = noPrinter // no type annotion here to force inlining
20+
val debug = noPrinter // no type annotation here to force inlining
2121
val derive: Printer = noPrinter
2222
val dottydoc: Printer = noPrinter
2323
val exhaustivity: Printer = noPrinter

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class ScalaSettings extends Settings.SettingGroup {
7070
val XprintTypes: Setting[Boolean] = BooleanSetting("-Xprint-types", "Print tree types (debugging option).")
7171
val XprintDiff: Setting[Boolean] = BooleanSetting("-Xprint-diff", "Print changed parts of the tree since last print.")
7272
val XprintDiffDel: Setting[Boolean] = BooleanSetting("-Xprint-diff-del", "Print changed parts of the tree since last print including deleted parts.")
73+
val XprintInline: Setting[Boolean] = BooleanSetting("-Xprint-inline", "Show where inlined code comes from")
7374
val Xprompt: Setting[Boolean] = BooleanSetting("-Xprompt", "Display a prompt after each error (debugging option).")
7475
val XmainClass: Setting[String] = StringSetting("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d <jar>)", "")
7576
val XnoValueClasses: Setting[Boolean] = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.")
@@ -157,7 +158,6 @@ class ScalaSettings extends Settings.SettingGroup {
157158
val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
158159
val YnoDoubleBindings: Setting[Boolean] = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")
159160
val YshowVarBounds: Setting[Boolean] = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds")
160-
val YshowNoInline: Setting[Boolean] = BooleanSetting("-Yshow-no-inline", "Show inlined code without the 'inlined from' info")
161161

162162
val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.")
163163

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1558,7 +1558,7 @@ object SymDenotations {
15581558
case p :: parents1 =>
15591559
p.classSymbol match {
15601560
case pcls: ClassSymbol => builder.addAll(pcls.baseClasses)
1561-
case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p")
1561+
case _ => assert(isRefinementClass || p.isError || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p")
15621562
}
15631563
traverse(parents1)
15641564
case nil =>

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
417417
"[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]"
418418
case tree @ Inlined(call, bindings, body) =>
419419
(("/* inlined from " ~ (if (call.isEmpty) "outside" else toText(call)) ~ " */ ") `provided`
420-
!homogenizedView && !ctx.settings.YshowNoInline.value) ~
420+
!homogenizedView && ctx.settings.XprintInline.value) ~
421421
blockText(bindings :+ body)
422422
case tpt: untpd.DerivedTypeTree =>
423423
"<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">"

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

+11-17
Original file line numberDiff line numberDiff line change
@@ -1367,9 +1367,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
13671367
(flip(tp1) relaxed_<:< flip(tp2)) || viewExists(tp1, tp2)
13681368
}
13691369

1370-
// # skipped implicit parameters in tp1 - # skipped implicit parameters in tp2
1371-
var implicitBalance: Int = 0
1372-
13731370
/** Widen the result type of synthetic implied methods from the implementation class to the
13741371
* type that's implemented. Example
13751372
*
@@ -1404,12 +1401,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14041401
}
14051402

14061403
/** Drop any implicit parameter section */
1407-
def stripImplicit(tp: Type, weight: Int): Type = tp match {
1404+
def stripImplicit(tp: Type): Type = tp match {
14081405
case mt: MethodType if mt.isImplicitMethod =>
1409-
implicitBalance += mt.paramInfos.length * weight
1410-
resultTypeApprox(mt)
1406+
stripImplicit(resultTypeApprox(mt))
14111407
case pt: PolyType =>
1412-
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight))
1408+
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType))
14131409
case _ =>
14141410
tp
14151411
}
@@ -1432,18 +1428,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14321428

14331429
val fullType1 = widenImplied(alt1.widen, alt1)
14341430
val fullType2 = widenImplied(alt2.widen, alt2)
1435-
val strippedType1 = stripImplicit(fullType1, -1)
1436-
val strippedType2 = stripImplicit(fullType2, +1)
1431+
val strippedType1 = stripImplicit(fullType1)
1432+
val strippedType2 = stripImplicit(fullType2)
14371433

14381434
val result = compareWithTypes(strippedType1, strippedType2)
1439-
if (result != 0 || !ctx.typerState.test(implicit ctx => strippedType1 =:= strippedType2))
1440-
result
1441-
else if (implicitBalance != 0)
1442-
-implicitBalance.signum
1443-
else if ((strippedType1 `ne` fullType1) || (strippedType2 `ne` fullType2))
1444-
compareWithTypes(fullType1, fullType2)
1445-
else
1446-
0
1435+
if (result != 0) result
1436+
else if (strippedType1 eq fullType1)
1437+
if (strippedType2 eq fullType2) 0 // no implicits either side: its' a draw
1438+
else 1 // prefer 1st alternative with no implicits
1439+
else if (strippedType2 eq fullType2) -1 // prefer 2nd alternative with no implicits
1440+
else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters
14471441
}}
14481442

14491443
def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -2597,7 +2597,10 @@ class Typer extends Namer
25972597
if (arg.tpe.isError) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil
25982598
}
25992599
tryEither { implicit ctx =>
2600-
typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt, locked)
2600+
val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs)
2601+
if (wtp.isContextual) app.pushAttachment(untpd.ApplyGiven, ())
2602+
typr.println(i"try with default implicit args $app")
2603+
typed(app, pt, locked)
26012604
} { (_, _) =>
26022605
issueErrors()
26032606
}

docs/docs/reference/changed-features/implicit-resolution.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,9 @@ affect implicits on the language level.
114114
An alternative A is _more specific_ than an alternative B if
115115

116116
- the relative weight of A over B is greater than the relative weight of B over A, or
117-
- the relative weights are the same, and the returned types of A and B are
118-
unifiable, and A takes more inferable parameters than B, or
119-
- the relative weights and the number of inferable parameters are the same, and
120-
the returned types of A and B are unifiable, and
121-
A is more specific than B if all inferable parameters in either alternative are
117+
- the relative weights are the same, and A takes no implicit parameters but B does, or
118+
- the relative weights are the same, both A and B take implicit parameters, and
119+
A is more specific than B if all implicit parameters in either alternative are
122120
replaced by regular parameters.
123121

124122
[//]: # todo: expand with precise rules

tests/pos/i5978.scala

+10-6
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,18 @@ package p3 {
6363
import implied TextParser._
6464
import TextParser._
6565

66-
val tp_v: TokenParser[Char, Position[CharSequence]] = TextParser.TP
67-
val tp_i = the[TokenParser[Char, Position[CharSequence]]]
68-
implicit val co_i: Conversion[Char, Position[CharSequence]] = the[Conversion[Char, Position[CharSequence]]]
69-
val co_x : Position[CharSequence] = 'x'
66+
val co_i: Conversion[Char, Position[CharSequence]] = the[Conversion[Char, Position[CharSequence]]]
7067

7168
{
72-
implied XXX for Conversion[Char, Position[CharSequence]] = co_i
73-
val co_y : Position[CharSequence] = 'x'
69+
val tp_v: TokenParser[Char, Position[CharSequence]] = TextParser.TP
70+
val tp_i = the[TokenParser[Char, Position[CharSequence]]]
71+
implied for Conversion[Char, Position[CharSequence]] = co_i
72+
val co_x : Position[CharSequence] = 'x'
73+
74+
{
75+
implied XXX for Conversion[Char, Position[CharSequence]] = co_i
76+
val co_y : Position[CharSequence] = 'x'
77+
}
7478
}
7579
}
7680
}

tests/run/implicit-functors.check

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
functorId
2+
funcorConst

tests/run/implicit-functors.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
object Utils {
2+
type Id[t] = t
3+
type Const[c] = [t] => c
4+
}
5+
6+
import Utils._
7+
8+
class Instances[F[_[_]], T[_]]
9+
10+
trait Functor[F[_]]
11+
12+
object Functor {
13+
implicit val functorId: Functor[Id] = { println("functorId"); null }
14+
implicit def functorGen[F[_]](implicit inst: Instances[Functor, F]): Functor[F] = { println("funcorGen"); null }
15+
implicit def functorConst[T]: Functor[Const[T]] = { println("funcorConst"); null }
16+
}
17+
18+
case class Sm[A](value: A)
19+
object Sm {
20+
implicit def smInstances[F[_[_]]]: Instances[F, Sm] = { println("smInstances"); null }
21+
}
22+
23+
object Test extends App {
24+
implicitly[Functor[Sm]]
25+
}

tests/run/implicit-specifity.scala

+13-5
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,27 @@ object Generic {
1212
implied showGen[T] given Generic for Show[T] = new Show[T](2)
1313
}
1414

15+
class Generic2
16+
object Generic2 {
17+
opaque type HiPriority = AnyRef
18+
implied showGen[T] for (Show[T] & HiPriority) = new Show[T](2).asInstanceOf
19+
}
20+
21+
class SubGen extends Generic
22+
object SubGen {
23+
implied for SubGen
24+
}
1525
object Contextual {
1626
trait Context
1727
implied ctx for Context
1828
implied showGen[T] given Generic for Show[T] = new Show[T](2)
1929
implied showGen[T] given Generic, Context for Show[T] = new Show[T](3)
30+
implied showGen[T] given SubGen for Show[T] = new Show[T](4)
2031
}
2132

2233
object Test extends App {
2334
assert(Show[Int] == 0)
2435
assert(Show[String] == 1)
25-
assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list
26-
27-
{ import implied Contextual._
28-
assert(Show[Generic] == 3)
29-
}
36+
assert(Show[Generic] == 1) // showGen loses against fallback due to longer argument list
37+
assert(Show[Generic2] == 2) // ... but the opaque type intersection trick works.
3038
}

tests/run/implied-divergence.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Checks that divergence checking works before going into
2+
// recursions.
3+
case class E(x: E | Null)
4+
5+
implied e for E(null)
6+
7+
object Test extends App {
8+
9+
implied f given (e: E) for E(e)
10+
11+
assert(the[E].toString == "E(E(null))")
12+
13+
}

tests/run/implied-priority.scala

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/* These tests show various mechanisms available for implicit prioritization.
2+
*/
3+
4+
class E[T](val str: String) // The type for which we infer terms below
5+
6+
class Arg[T] // An argument that we use as a given for some implied instances below
7+
8+
/* First, two schemes that require a pre-planned architecture for how and
9+
* where implied instances are defined.
10+
*
11+
* Traditional scheme: prioritize with location in class hierarchy
12+
*/
13+
class LowPriorityImplicits {
14+
implied t1[T] for E[T]("low")
15+
}
16+
17+
object NormalImplicits extends LowPriorityImplicits {
18+
implied t2[T] given Arg[T] for E[T]("norm")
19+
}
20+
21+
def test1 = {
22+
import implied NormalImplicits._
23+
assert(the[E[String]].str == "low") // No Arg available, so only t1 applies
24+
25+
{ implied for Arg[String]
26+
assert(the[E[String]].str == "norm") // Arg available, t2 takes priority
27+
}
28+
}
29+
30+
/* New scheme: dummy implicit arguments that indicate priorities
31+
*/
32+
object Priority {
33+
class Low
34+
object Low { implied for Low }
35+
36+
class High extends Low
37+
object High { implied for High }
38+
}
39+
40+
object Impl2 {
41+
implied t1[T] given Priority.Low for E[T]("low")
42+
implied t2[T] given Priority.High given Arg[T] for E[T]("norm")
43+
}
44+
45+
def test2 = {
46+
import implied Impl2._
47+
assert(the[E[String]].str == "low") // No Arg available, so only t1 applies
48+
49+
{ implied for Arg[String]
50+
assert(the[E[String]].str == "norm") // Arg available, t2 takes priority
51+
}
52+
}
53+
54+
/* The remaining tests show how we can add an override of highest priority or
55+
* a fallback of lowest priority to a group of existing implied instances, without
56+
* needing to change the location or definition of those instances.
57+
*
58+
* First, consider the problem how to define an override of highest priority.
59+
* If all of the alternatives in the existing hierarchy take implicit arguments,
60+
* an alternative without implicit arguments would override all of them.
61+
*/
62+
object Impl2a {
63+
implied t3[T] for E[T]("hi")
64+
}
65+
66+
def test2a = {
67+
import implied Impl2._
68+
import implied Impl2a._
69+
70+
implied for Arg[String]
71+
assert(the[E[String]].str == "hi")
72+
}
73+
74+
/* If that solution is not applicable, we can define an override by refining the
75+
* result type of the implied instance, e.g. like this:
76+
*/
77+
object Impl3 {
78+
implied t1[T] for E[T]("low")
79+
}
80+
81+
object Override {
82+
trait HighestPriority // A marker trait to indicate a higher priority
83+
84+
implied over[T] for E[T]("hi"), HighestPriority
85+
}
86+
87+
def test3 = {
88+
import implied Impl3._
89+
assert(the[E[String]].str == "low") // only t1 is available
90+
91+
{ import implied Override._
92+
import implied Impl3._
93+
assert(the[E[String]].str == "hi") // `over` takes priority since its result type is a subtype of t1's.
94+
}
95+
}
96+
97+
/* Now consider the dual problem: How to install a fallback with lower priority than existing
98+
* implied instances that kicks in when none of the other instances are applicable.
99+
* We get there in two stages. The first stage is by defining an explicit `withFallback` method
100+
* that takes the right implicit and returns it. This can be achieved using an implicit parameter
101+
* with a default argument.
102+
*/
103+
object Impl4 {
104+
implied t1 for E[String]("string")
105+
implied t2[T] given Arg[T] for E[T]("generic")
106+
}
107+
108+
object fallback4 {
109+
def withFallback[T] given (ev: E[T] = new E[T]("fallback")): E[T] = ev
110+
}
111+
112+
def test4 = {
113+
import implied Impl4._
114+
import fallback4._
115+
assert(withFallback[String].str == "string") // t1 is applicable
116+
assert(withFallback[Int].str == "fallback") // No applicable instances, pick the default
117+
118+
{ implied for Arg[Int]
119+
assert(withFallback[Int].str == "generic") // t2 is applicable
120+
}
121+
}
122+
123+
/* The final setup considers the problem how to define a fallback with lower priority than existing
124+
* implicits that exists as an implicit instance alongside the others. This can be achieved
125+
* by combining the implicit parameter with default technique for getting an existing impplicit
126+
* or a fallback with the result refinement technique for overriding all existing implicit instances.
127+
*
128+
* It employs a more re-usable version of the result refinement trick.
129+
*/
130+
opaque type HigherPriority = Any
131+
object HigherPriority {
132+
def inject[T](x: T): T & HigherPriority = x
133+
}
134+
135+
object fallback5 {
136+
implied [T] given (ev: E[T] = new E[T]("fallback")) for (E[T] & HigherPriority) = HigherPriority.inject(ev)
137+
}
138+
139+
def test5 = {
140+
import implied Impl4._
141+
import implied fallback5._
142+
143+
// All inferred terms go through the implied instance in fallback5.
144+
// They differ in what implicit argument is synthesized for that instance.
145+
assert(the[E[String]].str == "string") // t1 is applicable
146+
assert(the[E[Int]].str == "fallback") // No applicable instances, pick the default
147+
148+
{ implied for Arg[Int]
149+
assert(the[E[Int]].str == "generic") // t2 is applicable
150+
}
151+
}
152+
153+
object Test extends App {
154+
test1
155+
test2
156+
test2a
157+
test3
158+
test4
159+
test5
160+
}
161+

0 commit comments

Comments
 (0)