Skip to content

Change Implicit Resolution Rules #5887

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 6 commits into from
Feb 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ object Mode {
*/
val Printing: Mode = newMode(10, "Printing")

/** We are currently typechecking an ident to determine whether some implicit
* is shadowed - don't do any other shadowing tests.
*/
val ImplicitShadowing: Mode = newMode(11, "ImplicitShadowing")

/** We are currently in a `viewExists` check. In that case, ambiguous
* implicits checks are disabled and we succeed with the first implicit
* found.
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,6 @@ class PlainPrinter(_ctx: Context) extends Printer {
result.reason match {
case _: NoMatchingImplicits => "No Matching Implicit"
case _: DivergingImplicit => "Diverging Implicit"
case _: ShadowedImplicit => "Shadowed Implicit"
case result: AmbiguousImplicits =>
"Ambiguous Implicit: " ~ toText(result.alt1.ref) ~ " and " ~ toText(result.alt2.ref)
case _ =>
Expand Down
13 changes: 3 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1181,21 +1181,17 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
/** Compare to alternatives of an overloaded call or an implicit search.
*
* @param alt1, alt2 Non-overloaded references indicating the two choices
* @param level1, level2 If alternatives come from a comparison of two contextual
* implicit candidates, the nesting levels of the candidates.
* In all other cases the nesting levels are both 0.
* @return 1 if 1st alternative is preferred over 2nd
* -1 if 2nd alternative is preferred over 1st
* 0 if neither alternative is preferred over the other
*
* An alternative A1 is preferred over an alternative A2 if it wins in a tournament
* that awards one point for each of the following:
*
* - A1 is nested more deeply than A2
* - The nesting levels of A1 and A2 are the same, and A1's owner derives from A2's owner
* - A1's owner derives from A2's owner.
* - A1's type is more specific than A2's type.
*/
def compare(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {
def compare(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {

assert(alt1 ne alt2)

Expand Down Expand Up @@ -1305,10 +1301,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>

val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
val ownerScore =
if (nesting1 > nesting2) 1
else if (nesting1 < nesting2) -1
else compareOwner(owner1, owner2)
val ownerScore = compareOwner(owner1, owner2)

val tp1 = stripImplicit(alt1.widen)
val tp2 = stripImplicit(alt2.widen)
Expand Down
120 changes: 38 additions & 82 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -437,20 +437,6 @@ object Implicits {
em"${err.refStr(ref)} does not $qualify"
}

class ShadowedImplicit(ref: TermRef,
shadowing: Type,
val expectedType: Type,
val argument: Tree) extends SearchFailureType {
/** same as err.refStr but always prints owner even if it is a term */
def show(ref: Type)(implicit ctx: Context): String = ref match {
case ref: NamedType if ref.symbol.maybeOwner.isTerm =>
i"${ref.symbol} in ${ref.symbol.owner}"
case _ => err.refStr(ref)
}
def explanation(implicit ctx: Context): String =
em"${show(ref)} does $qualify but it is shadowed by ${show(shadowing)}"
}

class DivergingImplicit(ref: TermRef,
val expectedType: Type,
val argument: Tree) extends SearchFailureType {
Expand Down Expand Up @@ -534,27 +520,32 @@ trait ImplicitRunInfo { self: Run =>
val comps = new TermRefSet
tp match {
case tp: NamedType =>
val pre = tp.prefix
comps ++= iscopeRefs(pre)
def addRef(companion: TermRef): Unit = {
val compSym = companion.symbol
if (compSym is Package)
addRef(companion.select(nme.PACKAGE))
else if (compSym.exists)
comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef]
}
def addCompanionOf(sym: Symbol) = {
val companion = sym.companionModule
if (companion.exists) addRef(companion.termRef)
}
def addClassScope(cls: ClassSymbol): Unit = {
addCompanionOf(cls)
for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.classSymbol)))
addRef(ref)
if (!tp.symbol.is(Package) || ctx.scala2Mode) {
// Don't consider implicits in package prefixes unless under -language:Scala2
val pre = tp.prefix
comps ++= iscopeRefs(pre)
def addRef(companion: TermRef): Unit = {
val compSym = companion.symbol
if (compSym is Package) {
assert(ctx.scala2Mode)
addRef(companion.select(nme.PACKAGE))
}
else if (compSym.exists)
comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef]
}
def addCompanionOf(sym: Symbol) = {
val companion = sym.companionModule
if (companion.exists) addRef(companion.termRef)
}
def addClassScope(cls: ClassSymbol): Unit = {
addCompanionOf(cls)
for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.classSymbol)))
addRef(ref)
}
val underlyingTypeSym = tp.widen.typeSymbol
if (underlyingTypeSym.isOpaqueAlias) addCompanionOf(underlyingTypeSym)
else tp.classSymbols(liftingCtx).foreach(addClassScope)
}
val underlyingTypeSym = tp.widen.typeSymbol
if (underlyingTypeSym.isOpaqueAlias) addCompanionOf(underlyingTypeSym)
else tp.classSymbols(liftingCtx).foreach(addClassScope)
case _ =>
for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part)
}
Expand Down Expand Up @@ -836,9 +827,6 @@ trait Implicits { self: Typer =>
shortForm
case _ =>
arg.tpe match {
case tpe: ShadowedImplicit =>
i"""$headline;
|${tpe.explanation}."""
case tpe: SearchFailureType =>
i"""$headline.
|I found:
Expand Down Expand Up @@ -1034,9 +1022,6 @@ trait Implicits { self: Typer =>

private def isCoherent = pt.isRef(defn.EqlClass)

private val cmpContext = nestedContext()
private val cmpCandidates = (c1: Candidate, c2: Candidate) => compare(c1.ref, c2.ref, c1.level, c2.level)(cmpContext)

/** The expected type for the searched implicit */
lazy val fullProto: Type = implicitProto(pt, identity)

Expand All @@ -1050,9 +1035,9 @@ trait Implicits { self: Typer =>
/** Try to typecheck an implicit reference */
def typedImplicit(cand: Candidate, contextual: Boolean)(implicit ctx: Context): SearchResult = track("typedImplicit") { trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) {
val ref = cand.ref
var generated: Tree = tpd.ref(ref).withSpan(span.startPos)
val generated: Tree = tpd.ref(ref).withSpan(span.startPos)
val locked = ctx.typerState.ownedVars
val generated1 =
val adapted =
if (argument.isEmpty)
adapt(generated, pt, locked)
else {
Expand All @@ -1074,51 +1059,20 @@ trait Implicits { self: Typer =>
}
else tryConversion
}
lazy val shadowing =
typedUnadapted(untpd.Ident(cand.implicitRef.implicitName).withSpan(span.toSynthetic))(
nestedContext().addMode(Mode.ImplicitShadowing).setExploreTyperState())

/** Is candidate reference the same as the `shadowing` reference? (i.e.
* no actual shadowing occured). This is the case if the
* underlying symbol of the shadowing reference is the same as the
* symbol of the candidate reference, or if they have a common type owner.
*
* The second condition (same owner) is needed because the candidate reference
* and the potential shadowing reference are typechecked with different prototypes.
* so might yield different overloaded symbols. E.g. if the candidate reference
* is to an implicit conversion generated from an implicit class, the shadowing
* reference could go to the companion object of that class instead.
*/
def refSameAs(shadowing: Tree): Boolean = {
def symMatches(sym: Symbol): Boolean =
sym == ref.symbol || sym.owner.isType && sym.owner == ref.symbol.owner
def denotMatches(d: Denotation): Boolean = d match {
case d: SingleDenotation => symMatches(d.symbol)
case d => d.hasAltWith(denotMatches(_))
}
denotMatches(closureBody(shadowing).denot)
}

if (ctx.reporter.hasErrors) {
ctx.reporter.removeBufferedMessages
SearchFailure {
generated1.tpe match {
case _: SearchFailureType => generated1
case _ => generated1.withType(new MismatchedImplicit(ref, pt, argument))
adapted.tpe match {
case _: SearchFailureType => adapted
case _ => adapted.withType(new MismatchedImplicit(ref, pt, argument))
}
}
}
else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) &&
!shadowing.tpe.isError && !refSameAs(shadowing)) {
implicits.println(i"SHADOWING $ref in ${ref.termSymbol.maybeOwner} is shadowed by $shadowing in ${shadowing.symbol.maybeOwner}")
SearchFailure(generated1.withTypeUnchecked(
new ShadowedImplicit(ref, methPart(shadowing).tpe, pt, argument)))
}
else {
val generated2 =
if (cand.isExtension) Applications.ExtMethodApply(generated1).withType(generated1.tpe)
else generated1
SearchSuccess(generated2, ref, cand.level)(ctx.typerState, ctx.gadt)
val returned =
if (cand.isExtension) Applications.ExtMethodApply(adapted).withType(adapted.tpe)
else adapted
SearchSuccess(returned, ref, cand.level)(ctx.typerState, ctx.gadt)
}
}}

Expand All @@ -1143,12 +1097,14 @@ trait Implicits { self: Typer =>

/** Search a list of eligible implicit references */
def searchImplicits(eligible: List[Candidate], contextual: Boolean): SearchResult = {

/** Compare previous success with reference and level to determine which one would be chosen, if
* an implicit starting with the reference was found.
*/
def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int =
if (prev.ref eq ref) 0
else nestedContext().test(implicit ctx => compare(prev.ref, ref, prev.level, level))
else if (prev.level != level) prev.level - level
else nestedContext().test(implicit ctx => compare(prev.ref, ref))

/** If `alt1` is also a search success, try to disambiguate as follows:
* - If alt2 is preferred over alt1, pick alt2, otherwise return an
Expand Down Expand Up @@ -1326,7 +1282,7 @@ trait Implicits { self: Typer =>
case _: AmbiguousImplicits => failure2
case _ =>
reason match {
case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure
case (_: DivergingImplicit) => failure
case _ => List(failure, failure2).maxBy(_.tree.treeSize)
}
}
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2025,7 +2025,6 @@ class Typer extends Namer
val result = if (ifpt.exists &&
xtree.isTerm &&
!untpd.isContextualClosure(xtree) &&
!ctx.mode.is(Mode.ImplicitShadowing) &&
!ctx.mode.is(Mode.Pattern) &&
!ctx.isAfterTyper)
makeContextualFunction(xtree, ifpt)
Expand Down Expand Up @@ -2594,7 +2593,6 @@ class Typer extends Namer
!untpd.isContextualClosure(tree) &&
!isApplyProto(pt) &&
!ctx.mode.is(Mode.Pattern) &&
!ctx.mode.is(Mode.ImplicitShadowing) &&
!ctx.isAfterTyper) {
typr.println(i"insert apply on implicit $tree")
typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt, locked)
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/package-implicit/DataFlow.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package t1000647.bar

import t1000647.foo.{ScalaActorRef}

object DataFlow {
def foo(ref: ScalaActorRef) = ref.stop() // error: value stop is not a member of ...
}
7 changes: 0 additions & 7 deletions tests/new/package-implicit/DataFlow.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ object Test {

def outer(implicit c: C) = {

def f(c: C) = implicitly[C] // error: shadowing
def g(c: Int) = implicitly[C] // error: shadowing (even though type is different)
def f(c: C) = implicitly[C] // now ok: shadowing no longer tested
def g(c: Int) = implicitly[C] // now ok: shadowing no longer tested

f(new C)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/run/i5224.check
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
barInt
bar
barInt
2 changes: 2 additions & 0 deletions tests/run/i5224.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ object Test extends App {
def barInt: Unit = ???

implicitly[Bar[Int]]
// used to resolve to bar, but
// resolves to barInt now, since shadowing is no longer tested
}
}
1 change: 1 addition & 0 deletions tests/run/implicit-disambiguation.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
B
20 changes: 20 additions & 0 deletions tests/run/implicit-disambiguation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
abstract class A {
def show: String
}
class B extends A {
def show = "B"
}
class C extends A {
def show = "C"
}
object M {
def f given B, C : String = {
implied a for A = infer[B]
infer[A].show
}
}
object Test extends App {
implied b for B
implied c for C
println(M.f)
}