Skip to content

Change implicit resolution rule wrt implicit parameters #6071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 30, 2019
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 <jar>)", "")
val XnoValueClasses: Setting[Boolean] = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.")
Expand Down Expand Up @@ -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.")

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
"<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">"
Expand Down
28 changes: 11 additions & 17 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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
}
Expand All @@ -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") {
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
8 changes: 3 additions & 5 deletions docs/docs/reference/changed-features/implicit-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 10 additions & 6 deletions tests/pos/i5978.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/run/implicit-functors.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
functorId
funcorConst
25 changes: 25 additions & 0 deletions tests/run/implicit-functors.scala
Original file line number Diff line number Diff line change
@@ -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]]
}
18 changes: 13 additions & 5 deletions tests/run/implicit-specifity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
13 changes: 13 additions & 0 deletions tests/run/implied-divergence.scala
Original file line number Diff line number Diff line change
@@ -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))")

}
161 changes: 161 additions & 0 deletions tests/run/implied-priority.scala
Original file line number Diff line number Diff line change
@@ -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
}

Loading