diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 0fb504f62d12..8ae79347fc0f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -69,9 +69,9 @@ object Trees { } /** A unique identifier for this tree. Used for debugging, and potentially - * tracking presentation compiler interactions + * tracking presentation compiler interactions. */ - private var myUniqueId: Int = nxId + @sharable private var myUniqueId: Int = nxId def uniqueId = myUniqueId @@ -370,6 +370,11 @@ object Trees { override def toString = s"BackquotedIdent($name)" } + class SearchFailureIdent[-T >: Untyped] private[ast] (name: Name) + extends Ident[T](name) { + override def toString = s"SearchFailureIdent($name)" + } + /** qualifier.name, or qualifier#name, if qualifier is a type */ case class Select[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name) extends RefTree[T] { @@ -830,6 +835,7 @@ object Trees { type Ident = Trees.Ident[T] type BackquotedIdent = Trees.BackquotedIdent[T] + type SearchFailureIdent = Trees.SearchFailureIdent[T] type Select = Trees.Select[T] type SelectWithSig = Trees.SelectWithSig[T] type This = Trees.This[T] diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index bf40539679ec..aae73844958c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -247,6 +247,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Ident(name: Name): Ident = new Ident(name) def BackquotedIdent(name: Name): BackquotedIdent = new BackquotedIdent(name) + def SearchFailureIdent(name: Name): SearchFailureIdent = new SearchFailureIdent(name) def Select(qualifier: Tree, name: Name): Select = new Select(qualifier, name) def SelectWithSig(qualifier: Tree, name: Name, sig: Signature): Select = new SelectWithSig(qualifier, name, sig) def This(qual: Ident): This = new This(qual) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f921ce5041a9..646fbfcb157a 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -26,7 +26,6 @@ class ScalaSettings extends Settings.SettingGroup { val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.") val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding) val explainTypes = BooleanSetting("-explain-types", "Explain type errors in more detail.") - val explainImplicits = BooleanSetting("-explain-implicits", "Explain implicit search errors in more detail.") val explain = BooleanSetting("-explain", "Explain errors in more detail.") val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.") val help = BooleanSetting("-help", "Print a synopsis of standard options") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 941be543b2b6..feb5c2e7efcb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -584,6 +584,12 @@ class Definitions { def Eq_eqAny(implicit ctx: Context) = EqModule.requiredMethod(nme.eqAny) + lazy val NotType = ctx.requiredClassRef("scala.implicits.Not") + def NotClass(implicit ctx: Context) = NotType.symbol.asClass + def NotModule(implicit ctx: Context) = NotClass.companionModule + + def Not_value(implicit ctx: Context) = NotModule.requiredMethod(nme.value) + lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope") // Annotation base classes diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index f7e6926cfe92..532d7f90d408 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -89,7 +89,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab private[this] var testReporter: StoreReporter = null /** Test using `op`, restoring typerState to previous state afterwards */ - def test(op: => Boolean): Boolean = { + def test[T](op: => T): T = { val savedReporter = myReporter val savedConstraint = myConstraint val savedCommittable = myIsCommittable diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 897b8a4b65b9..c53968543bcb 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1978,7 +1978,7 @@ object Types { else candidate def withPrefix(prefix: Type)(implicit ctx: Context): NamedType = designator match { - case designator: TermSymbol => + case designator: TermSymbol @unchecked => TermRef(prefix, designator) case _ => // If symbol exists, the new signature is the symbol's signature as seen @@ -3638,22 +3638,25 @@ object Types { */ abstract class FlexType extends UncachedGroundType with ValueType - class ErrorType private[Types] () extends FlexType { - def msg(implicit ctx: Context): Message = - ctx.errorTypeMsg.get(this) match { - case Some(msgFun) => msgFun() - case None => "error message from previous run no longer available" - } + abstract class ErrorType extends FlexType { + def msg(implicit ctx: Context): Message } + object ErrorType { def apply(msg: => Message)(implicit ctx: Context): ErrorType = { - val et = new ErrorType + val et = new ErrorType { + def msg(implicit ctx: Context): Message = + ctx.errorTypeMsg.get(this) match { + case Some(msgFun) => msgFun() + case None => "error message from previous run no longer available" + } + } ctx.base.errorTypeMsg(et) = () => msg et } } - object UnspecifiedErrorType extends ErrorType() { + object UnspecifiedErrorType extends ErrorType { override def msg(implicit ctx: Context): Message = "unspecified error" } diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index fc291a7b5620..1689ac392031 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -171,8 +171,8 @@ class PlainPrinter(_ctx: Context) extends Printer { changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) } case OrType(tp1, tp2) => changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) } - case _: ErrorType => - "" + case tp: ErrorType => + s"" case tp: WildcardType => if (tp.optBounds.exists) "(?" ~ toTextRHS(tp.bounds) ~ ")" else "?" case NoType => @@ -202,8 +202,6 @@ class PlainPrinter(_ctx: Context) extends Printer { ParamRefNameString(tp) ~ ".type" case AnnotatedType(tpe, annot) => toTextLocal(tpe) ~ " " ~ toText(annot) - case AppliedType(tycon, args) => - toTextLocal(tycon) ~ "[" ~ Text(args.map(argText), ", ") ~ "]" case tp: TypeVar => if (tp.isInstantiated) toTextLocal(tp.instanceOpt) ~ ("^" provided ctx.settings.YprintDebug.value) @@ -501,18 +499,16 @@ class PlainPrinter(_ctx: Context) extends Printer { def toText(result: SearchResult): Text = result match { case result: SearchSuccess => "SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree) - case _: NonMatchingImplicit | NoImplicitMatches => - "NoImplicitMatches" - case _: DivergingImplicit | DivergingImplicit => - "Diverging Implicit" - case result: ShadowedImplicit => - "Shadowed Implicit" - case result: FailedImplicit => - "Failed Implicit" - case result: AmbiguousImplicits => - "Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2) - case _ => - "?Unknown Implicit Result?" + result.getClass + case result: SearchFailure => + 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 _ => + "?Unknown Implicit Result?" + result.getClass + } } def toText(importInfo: ImportInfo): Text = { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 8e4e3fc160f1..c9dd0d0046c4 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -7,7 +7,7 @@ import TypeErasure.ErasedValueType import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation import StdNames.{nme, tpnme} import ast.{Trees, untpd, tpd} -import typer.{Namer, Inliner} +import typer.{Namer, Inliner, Implicits} import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType} import Trees._ import TypeApplications._ @@ -332,6 +332,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def toTextCore(tree: Tree): Text = tree match { case id: Trees.BackquotedIdent[_] if !homogenizedView => "`" ~ toText(id.name) ~ "`" + case id: Trees.SearchFailureIdent[_] => + tree.typeOpt match { + case reason: Implicits.SearchFailureType => + toText(id.name) ~ "implicitly[" ~ toText(reason.expectedType) ~ "]" + case _ => + toText(id.name) + } case Ident(name) => tree.typeOpt match { case tp: NamedType if name != nme.WILDCARD => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 3ac531eed049..e1a3cd830221 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1031,33 +1031,43 @@ trait Applications extends Compatibility { self: Typer with Dynamic => tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) } - /** In a set of overloaded applicable alternatives, is `alt1` at least as good as - * `alt2`? Also used for implicits disambiguation. + /** Compare owner inheritance level. + * @param sym1 The first owner + * @param sym2 The second owner + * @return 1 if `sym1` properly derives from `sym2` + * -1 if `sym2` properly derives from `sym1` + * 0 otherwise + * Module classes also inherit the relationship from their companions. + */ + def compareOwner(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Int = + if (sym1 == sym2) 0 + else if (sym1 isSubClass sym2) 1 + else if (sym2 isSubClass sym1) -1 + else if (sym2 is Module) compareOwner(sym1, sym2.companionClass) + else if (sym1 is Module) compareOwner(sym1.companionClass, sym2) + else 0 + + /** 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 "as good as" an alternative A2 if it wins or draws in a tournament - * that awards one point for each of the following + * 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 type is more specific than A2's type. */ - def isAsGood(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Boolean = track("isAsGood") { trace(i"isAsGood($alt1, $alt2)", overload) { + def compare(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) { assert(alt1 ne alt2) - /** Is class or module class `sym1` derived from class or module class `sym2`? - * Module classes also inherit the relationship from their companions. - */ - def isDerived(sym1: Symbol, sym2: Symbol): Boolean = - if (sym1 isSubClass sym2) true - else if (sym2 is Module) isDerived(sym1, sym2.companionClass) - else (sym1 is Module) && isDerived(sym1.companionClass, sym2) - /** Is alternative `alt1` with type `tp1` as specific as alternative * `alt2` with type `tp2` ? * @@ -1165,55 +1175,56 @@ 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 tp1 = stripImplicit(alt1.widen) val tp2 = stripImplicit(alt2.widen) - - def winsOwner1 = - nesting1 > nesting2 || nesting1 == nesting2 && isDerived(owner1, owner2) def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2) - def winsOwner2 = - nesting2 > nesting1 || nesting1 == nesting2 && isDerived(owner2, owner1) def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) - overload.println(i"isAsGood($alt1, $alt2)? $tp1 $tp2 $winsOwner1 $winsType1 $winsOwner2 $winsType2") - - // Assume the following probabilities: - // - // P(winsOwnerX) = 2/3 - // P(winsTypeX) = 1/3 - // - // Then the call probabilities of the 4 basic operations are as follows: - // - // winsOwner1: 1/1 - // winsOwner2: 1/1 - // winsType1 : 7/9 - // winsType2 : 4/9 - - if (winsOwner1) /* 6/9 */ !winsOwner2 || /* 4/9 */ winsType1 || /* 8/27 */ !winsType2 - else if (winsOwner2) /* 2/9 */ winsType1 && /* 2/27 */ !winsType2 - else /* 1/9 */ winsType1 || /* 2/27 */ !winsType2 + overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2") + + if (ownerScore == 1) + if (winsType1 || !winsType2) 1 else 0 + else if (ownerScore == -1) + if (winsType2 || !winsType1) -1 else 0 + else if (winsType1) + if (winsType2) 0 else 1 + else + if (winsType2) -1 else 0 }} def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { alts match { case Nil => alts case _ :: Nil => alts + case alt1 :: alt2 :: Nil => + compare(alt1, alt2) match { + case 1 => alt1 :: Nil + case -1 => alt2 :: Nil + case 0 => alts + } case alt :: alts1 => - def winner(bestSoFar: TermRef, alts: List[TermRef]): TermRef = alts match { + def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - winner(if (isAsGood(alt, bestSoFar)) alt else bestSoFar, alts1) - case nil => - bestSoFar + compare(previous.head, alt) match { + case 1 => survivors(previous, alts1) + case -1 => survivors(alt :: previous.tail, alts1) + case 0 => survivors(alt :: previous, alts1) + } + case Nil => previous } - val best = winner(alt, alts1) + val best :: rest = survivors(alt :: Nil, alts1) def asGood(alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - if ((alt eq best) || !isAsGood(alt, best)) asGood(alts1) - else alt :: asGood(alts1) + if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1) case nil => Nil } - best :: asGood(alts) + best :: asGood(rest) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 1d6643763365..ee5e9aab7195 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -150,9 +150,8 @@ trait Dynamic { self: Typer with Applications => fail(i"""takes too many parameters. |Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""") else { - def issueError(msgFn: String => String): Unit = ctx.error(msgFn(""), tree.pos) val ctags = tpe.paramInfos.map(pt => - inferImplicitArg(defn.ClassTagType.appliedTo(pt :: Nil), issueError, tree.pos.endPos)) + implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos)) structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType()) } case tpe: ValueType => diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 9714549b6bee..0427b4c76a66 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -104,11 +104,11 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? - def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = { + def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = { val normTp = normalize(tree.tpe, pt) val treeTp = if (normTp <:< pt) tree.tpe else normTp // use normalized type if that also shows an error, original type otherwise - errorTree(tree, typeMismatchMsg(treeTp, pt, implicitFailure.postscript)) + errorTree(tree, typeMismatchMsg(treeTp, pt, implicitFailure.whyNoConversion)) } /** A subtype log explaining why `found` does not conform to `expected` */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 657f10564787..e922a1a55c14 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -15,6 +15,7 @@ import TypeErasure.{erasure, hasStableErasure} import Mode.ImplicitsEnabled import Denotations._ import NameOps._ +import NameKinds.LazyImplicitName import SymDenotations._ import Symbols._ import Types._ @@ -25,7 +26,7 @@ import Constants._ import Applications._ import ProtoTypes._ import ErrorReporting._ -import reporting.diagnostic.MessageContainer +import reporting.diagnostic.{Message, MessageContainer} import Inferencing.fullyDefinedType import Trees._ import Hashable._ @@ -37,6 +38,7 @@ import reporting.trace /** Implicit resolution */ object Implicits { + import tpd._ /** A reference to an implicit value to be made visible on the next nested call to * inferImplicitArg with a by-name expected type. @@ -185,7 +187,8 @@ object Implicits { if (outerImplicits == null) 1 else if (ctx.scala2Mode || (ctx.owner eq outerImplicits.ctx.owner) && - (ctx.scope eq outerImplicits.ctx.scope)) outerImplicits.level + (ctx.scope eq outerImplicits.ctx.scope) && + !refs.head.name.is(LazyImplicitName)) outerImplicits.level else outerImplicits.level + 1 /** Is this the outermost implicits? This is the case if it either the implicits @@ -253,105 +256,101 @@ object Implicits { /** The result of an implicit search */ sealed abstract class SearchResult extends Showable { + def tree: Tree def toText(printer: Printer): Text = printer.toText(this) + def recoverWith(other: SearchFailure => SearchResult) = this match { + case _: SearchSuccess => this + case fail: SearchFailure => other(fail) + } + def isSuccess = isInstanceOf[SearchSuccess] } /** A successful search - * @param ref The implicit reference that succeeded - * @param tree The typed tree that needs to be inserted - * @param ctx The context after the implicit search + * @param tree The typed tree that needs to be inserted + * @param ref The implicit reference that succeeded + * @param level The level where the reference was found + * @param tstate The typer state to be committed if this alternative is chosen */ - case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult with Showable { - override def toString = s"SearchSuccess($tree, $ref, $level)" - } + case class SearchSuccess(tree: Tree, ref: TermRef, level: Int)(val tstate: TyperState) extends SearchResult with Showable /** A failed search */ - abstract class SearchFailure extends SearchResult { - /** A note describing the failure in more detail - this - * is either empty or starts with a '\n' - */ - def postscript(implicit ctx: Context): String = "" + case class SearchFailure(tree: Tree) extends SearchResult { + final def isAmbiguous = tree.tpe.isInstanceOf[AmbiguousImplicits] + final def reason = tree.tpe.asInstanceOf[SearchFailureType] } - /** A "no matching implicit found" failure */ - case object NoImplicitMatches extends SearchFailure + object SearchFailure { + def apply(tpe: SearchFailureType): SearchFailure = { + val id = + if (tpe.isInstanceOf[AmbiguousImplicits]) "/* ambiguous */" + else "/* missing */" + SearchFailure(untpd.SearchFailureIdent(id.toTermName).withTypeUnchecked(tpe)) + } + } - case object DivergingImplicit extends SearchFailure + abstract class SearchFailureType extends ErrorType { + def expectedType: Type + protected def argument: Tree - /** A search failure that can show information about the cause */ - abstract class ExplainedSearchFailure extends SearchFailure { - protected def pt: Type - protected def argument: tpd.Tree - protected def qualify(implicit ctx: Context) = - if (argument.isEmpty) em"match type $pt" - else em"convert from ${argument.tpe} to $pt" + final protected def qualify(implicit ctx: Context) = + if (expectedType.exists) + if (argument.isEmpty) em"match type $expectedType" + else em"convert from ${argument.tpe} to $expectedType" + else + if (argument.isEmpty) em"match expected type" + else em"convert from ${argument.tpe} to expected type" /** An explanation of the cause of the failure as a string */ def explanation(implicit ctx: Context): String + + def msg(implicit ctx: Context): Message = explanation + + /** If search was for an implicit conversion, a note describing the failure + * in more detail - this is either empty or starts with a '\n' + */ + def whyNoConversion(implicit ctx: Context): String = "" } + class NoMatchingImplicits(val expectedType: Type, val argument: Tree) extends SearchFailureType { + def explanation(implicit ctx: Context): String = + em"no implicit values were found that $qualify" + } + + @sharable object NoMatchingImplicits extends NoMatchingImplicits(NoType, EmptyTree) + + @sharable val NoMatchingImplicitsFailure: SearchFailure = + SearchFailure(NoMatchingImplicits) + /** An ambiguous implicits failure */ - class AmbiguousImplicits(val alt1: TermRef, val alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType { def explanation(implicit ctx: Context): String = - em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify" - override def postscript(implicit ctx: Context) = + em"both ${err.refStr(alt1.ref)} and ${err.refStr(alt2.ref)} $qualify" + override def whyNoConversion(implicit ctx: Context) = "\nNote that implicit conversions cannot be applied because they are ambiguous;" + "\n " + explanation } - class NonMatchingImplicit(ref: TermRef, - val pt: Type, - val argument: tpd.Tree, - trail: List[MessageContainer]) extends ExplainedSearchFailure { - private val separator = "\n**** because ****\n" - - /** Replace repeated parts beginning with `separator` by ... */ - private def elideRepeated(str: String): String = { - val startIdx = str.indexOfSlice(separator) - val nextIdx = str.indexOfSlice(separator, startIdx + separator.length) - if (nextIdx < 0) str - else { - val prefix = str.take(startIdx) - val first = str.slice(startIdx, nextIdx) - var rest = str.drop(nextIdx) - if (rest.startsWith(first)) { - rest = rest.drop(first.length) - val dots = "\n\n ...\n" - if (!rest.startsWith(dots)) rest = dots ++ rest - } - prefix ++ first ++ rest - } - } - - def explanation(implicit ctx: Context): String = { - val headMsg = em"${err.refStr(ref)} does not $qualify" - val trailMsg = trail.map(mc => i"$separator ${mc.message}").mkString - elideRepeated(headMsg ++ trailMsg) - } + class MismatchedImplicit(ref: TermRef, + val expectedType: Type, + val argument: Tree) extends SearchFailureType { + def explanation(implicit ctx: Context): String = + em"${err.refStr(ref)} does not $qualify" } - class ShadowedImplicit(ref: TermRef, shadowing: Type, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + class ShadowedImplicit(ref: TermRef, + shadowing: Type, + val expectedType: Type, + val argument: Tree) extends SearchFailureType { def explanation(implicit ctx: Context): String = em"${err.refStr(ref)} does $qualify but is shadowed by ${err.refStr(shadowing)}" } - class DivergingImplicit(ref: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + class DivergingImplicit(ref: TermRef, + val expectedType: Type, + val argument: Tree) extends SearchFailureType { def explanation(implicit ctx: Context): String = em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } - - class FailedImplicit(failures: List[ExplainedSearchFailure], val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { - def explanation(implicit ctx: Context): String = - if (failures.isEmpty) s" No implicit candidates were found that $qualify" - else failures.map(_.explanation).mkString("\n").replace("\n", "\n ") - override def postscript(implicit ctx: Context): String = { - val what = - if (argument.isEmpty) i"value of type $pt" - else i"conversion from ${argument.tpe.widen} to $pt" - i""" - |$explanation""" - } - } } import Implicits._ @@ -487,14 +486,8 @@ trait ImplicitRunInfo { self: RunInfo => iscope(rootTp) } - /** A map that counts the number of times an implicit ref was picked */ - val useCount = new mutable.HashMap[TermRef, Int] { - override def default(key: TermRef) = 0 - } - def clear() = { implicitScopeCache.clear() - useCount.clear() } } @@ -511,8 +504,7 @@ trait Implicits { self: Typer => && from.isValueType && ( from.isValueSubType(to) || inferView(dummyTreeOfType(from), to) - (ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()) - .isInstanceOf[SearchSuccess] + (ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()).isSuccess // TODO: investigate why we can't TyperState#test here ) ) @@ -527,7 +519,7 @@ trait Implicits { self: Typer => || (from.tpe isRef defn.NothingClass) || (from.tpe isRef defn.NullClass) || !(ctx.mode is Mode.ImplicitsEnabled) - || (from.tpe eq NoPrefix)) NoImplicitMatches + || (from.tpe eq NoPrefix)) NoMatchingImplicitsFailure else { def adjust(to: Type) = to.stripTypeVar.widenExpr match { case SelectionProto(name, memberProto, compat, true) => @@ -550,7 +542,7 @@ trait Implicits { self: Typer => * which is itself parameterized by another string, * indicating where the implicit parameter is needed */ - def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = { + def inferImplicitArg(formal: Type, pos: Position)(implicit ctx: Context): Tree = { /** If `formal` is of the form ClassTag[T], where `T` is a class type, * synthesize a class tag for `T`. @@ -560,11 +552,9 @@ trait Implicits { self: Typer => case arg :: Nil => fullyDefinedType(arg, "ClassTag argument", pos) match { case defn.ArrayOf(elemTp) => - val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos) - if (etag.isEmpty) etag else etag.select(nme.wrap) - case tp if hasStableErasure(tp) => - if (defn.isBottomClass(tp.typeSymbol)) - error(where => i"attempt to take ClassTag of undetermined type for $where") + val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), pos) + if (etag.tpe.isError) EmptyTree else etag.select(nme.wrap) + case tp if hasStableErasure(tp) && !defn.isBottomClass(tp.typeSymbol) => ref(defn.ClassTagModule) .select(nme.apply) .appliedToType(tp) @@ -593,7 +583,7 @@ trait Implicits { self: Typer => } def hasEq(tp: Type): Boolean = - inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isInstanceOf[SearchSuccess] + inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isSuccess def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = { List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos)) @@ -623,7 +613,7 @@ trait Implicits { self: Typer => } inferImplicit(formalValue, EmptyTree, pos)(argCtx) match { - case SearchSuccess(arg, _, _, _) => + case SearchSuccess(arg, _, _) => def refersToLazyImplicit = arg.existsSubTree { case id: Ident => id.symbol == lazyImplicit case _ => false @@ -632,37 +622,69 @@ trait Implicits { self: Typer => Block(ValDef(lazyImplicit.asTerm, arg).withPos(pos) :: Nil, ref(lazyImplicit)) else arg + case fail @ SearchFailure(tree) => + if (fail.isAmbiguous) + tree + else if (formalValue.isRef(defn.ClassTagClass)) + synthesizedClassTag(formalValue).orElse(tree) + else if (formalValue.isRef(defn.EqClass)) + synthesizedEq(formalValue).orElse(tree) + else + tree + } + } + + /** Search an implicit argument and report error if not found */ + def implicitArgTree(formal: Type, pos: Position)(implicit ctx: Context): Tree = { + val arg = inferImplicitArg(formal, pos) + if (arg.tpe.isInstanceOf[SearchFailureType]) ctx.error(missingArgMsg(arg, formal, ""), pos) + arg + } + + def missingArgMsg(arg: Tree, pt: Type, where: String)(implicit ctx: Context): String = { + def msg(shortForm: String)(headline: String = shortForm) = arg match { + case arg: Trees.SearchFailureIdent[_] => + shortForm + case _ => + i"""$headline. + |I found: + | + | ${arg.show.replace("\n", "\n ")} + | + |But ${arg.tpe.asInstanceOf[SearchFailureType].explanation}.""" + } + arg.tpe match { case ambi: AmbiguousImplicits => - error(where => s"ambiguous implicits: ${ambi.explanation} of $where") - EmptyTree - case failure: SearchFailure => - val arg = - if (formalValue.isRef(defn.ClassTagClass)) - synthesizedClassTag(formalValue) - else if (formalValue.isRef(defn.EqClass)) - synthesizedEq(formalValue) - else - EmptyTree - if (!arg.isEmpty) arg - else { - var msgFn = (where: String) => - em"no implicit argument of type $formal found for $where" + failure.postscript + msg(s"ambiguous implicit arguments: ${ambi.explanation} of $where")( + s"ambiguous implicit arguments of type ${pt.show} found for $where") + case _ => + val userDefined = for { - notFound <- formalValue.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) + notFound <- pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) Trees.Literal(Constant(raw: String)) <- notFound.argument(0) - } { - msgFn = where => - err.implicitNotFoundString( - raw, - formalValue.typeSymbol.typeParams.map(_.name.unexpandedName.toString), - formalValue.argInfos) } - error(msgFn) - EmptyTree - } + yield { + err.implicitNotFoundString( + raw, + pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), + pt.argInfos) + } + msg(userDefined.getOrElse(em"no implicit argument of type $pt was found for $where"))() } } + /** A string indicating the formal parameter corresponding to a missing argument */ + def implicitParamString(paramName: TermName, methodStr: String, tree: Tree)(implicit ctx: Context): String = + tree match { + case Select(qual, nme.apply) if defn.isFunctionType(qual.tpe.widen) => + val qt = qual.tpe.widen + val qt1 = qt.dealias + def addendum = if (qt1 eq qt) "" else (i"\nwhich is an alias of: $qt1") + em"parameter of ${qual.tpe.widen}$addendum" + case _ => + em"parameter ${paramName} of $methodStr" + } + private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { def eqNullable: Boolean = { val other = @@ -692,8 +714,7 @@ trait Implicits { self: Typer => /** Check that equality tests between types `ltp` and `rtp` make sense */ def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) { - val res = inferImplicitArg( - defn.EqType.appliedTo(ltp, rtp), msgFun => ctx.error(msgFun(""), pos), pos) + val res = implicitArgTree(defn.EqType.appliedTo(ltp, rtp), pos) implicits.println(i"Eq witness found for $ltp / $rtp: $res: ${res.tpe}") } @@ -710,24 +731,21 @@ trait Implicits { self: Typer => else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}") trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { assert(!pt.isInstanceOf[ExprType]) - val isearch = - if (ctx.settings.explainImplicits.value) new ExplainedImplicitSearch(pt, argument, pos) - else new ImplicitSearch(pt, argument, pos) - val result = isearch.bestImplicit(contextual = true) + val result = new ImplicitSearch(pt, argument, pos).bestImplicit(contextual = true) result match { case result: SearchSuccess => result.tstate.commit() implicits.println(i"success: $result") implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}") result - case result: AmbiguousImplicits => + case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto if (deepPt ne pt) inferImplicit(deepPt, argument, pos) else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) { inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match { case altResult: SearchSuccess => ctx.migrationWarning( - s"According to new implicit resolution rules, this will be ambiguous:\n ${result.explanation}", + s"According to new implicit resolution rules, this will be ambiguous:\n ${result.reason.explanation}", pos) altResult case _ => @@ -743,8 +761,10 @@ trait Implicits { self: Typer => /** An implicit search; parameters as in `inferImplicit` */ class ImplicitSearch(protected val pt: Type, protected val argument: Tree, pos: Position)(implicit ctx: Context) { + assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], + em"found: $argument: ${argument.tpe}, expected: $pt") - private def nestedContext = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) + private def nestedContext() = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) private def implicitProto(resultType: Type, f: Type => Type) = if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) @@ -752,8 +772,8 @@ trait Implicits { self: Typer => private def isCoherent = pt.isRef(defn.EqClass) - assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], - em"found: $argument: ${argument.tpe}, expected: $pt") + 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 = implicitProto(pt, identity) @@ -767,16 +787,14 @@ trait Implicits { self: Typer => /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ val wildProto = implicitProto(pt, wildApprox(_, null, Set.empty)) - /** Search failures; overridden in ExplainedImplicitSearch */ - protected def nonMatchingImplicit(ref: TermRef, trail: List[MessageContainer]): SearchFailure = NoImplicitMatches - protected def divergingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches - protected def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure = NoImplicitMatches - protected def failedSearch: SearchFailure = NoImplicitMatches + val isNot = wildProto.classSymbol == defn.NotClass /** Search a list of eligible implicit references */ def searchImplicits(eligible: List[Candidate], contextual: Boolean): SearchResult = { val constr = ctx.typerState.constraint + //println(i"search implicits $pt / ${eligible.map(_.ref)}") + /** Try to typecheck an implicit reference */ def typedImplicit(cand: Candidate)(implicit ctx: Context): SearchResult = track("typedImplicit") { trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { assert(constr eq ctx.typerState.constraint) @@ -789,7 +807,7 @@ trait Implicits { self: Typer => val generated1 = adapt(generated, pt) lazy val shadowing = typed(untpd.Ident(ref.name) withPos pos.toSynthetic, funProto)( - nestedContext.addMode(Mode.ImplicitShadowing).setExploreTyperState()) + nestedContext().addMode(Mode.ImplicitShadowing).setExploreTyperState()) def refSameAs(shadowing: Tree): Boolean = ref.symbol == closureBody(shadowing).symbol || { shadowing match { @@ -800,137 +818,215 @@ trait Implicits { self: Typer => } } - if (ctx.reporter.hasErrors) - nonMatchingImplicit(ref, ctx.reporter.removeBufferedMessages) + if (ctx.reporter.hasErrors) { + ctx.reporter.removeBufferedMessages + SearchFailure { + generated1.tpe match { + case _: SearchFailureType => generated1 + case _ => generated1.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.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}") - shadowedImplicit(ref, methPart(shadowing).tpe) + 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 - SearchSuccess(generated1, ref, cand.level, ctx.typerState) + SearchSuccess(generated1, ref, cand.level)(ctx.typerState) }} - /** Given a list of implicit references, produce a list of all implicit search successes, - * where the first is supposed to be the best one. - * @param pending The list of implicit references that remain to be investigated - * @param acc An accumulator of successful matches found so far. + /** Try to type-check implicit reference, after checking that this is not + * a diverging search */ - def rankImplicits(pending: List[Candidate], acc: List[SearchSuccess]): List[SearchSuccess] = pending match { - case cand :: pending1 => - val history = ctx.searchHistory nest wildProto - val result = - if (history eq ctx.searchHistory) divergingImplicit(cand.ref) - else typedImplicit(cand)(nestedContext.setNewTyperState().setSearchHistory(history)) - result match { - case fail: SearchFailure => - rankImplicits(pending1, acc) - case best: SearchSuccess => - if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) best :: Nil - else { - val newPending = pending1.filter(cand1 => - ctx.typerState.test(isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext))) - rankImplicits(newPending, best :: acc) - } - } - case nil => acc + def tryImplicit(cand: Candidate): SearchResult = { + val history = ctx.searchHistory nest wildProto + if (history eq ctx.searchHistory) + SearchFailure(new DivergingImplicit(cand.ref, pt, argument)) + else + typedImplicit(cand)(nestedContext().setNewTyperState().setSearchHistory(history)) } - /** If the (result types of) the expected type, and both alternatives - * are all numeric value types, return the alternative which has - * the smaller numeric subtype as result type, if it exists. - * (This alternative is then discarded). + /** Compare previous success with reference and level to determine which one would be chosen, if + * an implicit starting with the reference was found. */ - def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess): SearchResult = { + def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int = + if (prev.ref eq ref) 0 + else ctx.typerState.test(compare(prev.ref, ref, prev.level, level)(nestedContext())) + + /* Seems we don't need this anymore. + def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess) = { def isNumeric(tp: Type) = tp.typeSymbol.isNumericValueClass def isProperSubType(tp1: Type, tp2: Type) = tp1.isValueSubType(tp2) && !tp2.isValueSubType(tp1) - val rpt = pt.resultType - val rt1 = alt1.ref.widen.resultType - val rt2 = alt2.ref.widen.resultType + val rpt = pt.resultType + val rt1 = alt1.ref.widen.resultType + val rt2 = alt2.ref.widen.resultType if (isNumeric(rpt) && isNumeric(rt1) && isNumeric(rt2)) - if (isProperSubType(rt1, rt2)) alt1 - else if (isProperSubType(rt2, rt1)) alt2 - else NoImplicitMatches - else NoImplicitMatches + if (isProperSubType(rt1, rt2)) alt2 + else if (isProperSubType(rt2, rt1)) alt1 + else NoMatchingImplicitsFailure + else NoMatchingImplicitsFailure } + */ - /** Convert a (possibly empty) list of search successes into a single search result */ - def condense(hits: List[SearchSuccess]): SearchResult = hits match { - case best :: alts => - alts.find(alt => - ctx.typerState.test(isAsGood(alt.ref, best.ref, alt.level, best.level))) match { - case Some(alt) => - typr.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}") - /* !!! DEBUG - println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}") - isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState) - */ - numericValueTieBreak(best, alt) match { - case eliminated: SearchSuccess => condense(hits.filter(_ ne eliminated)) - case _ => new AmbiguousImplicits(best.ref, alt.ref, pt, argument) - } - case None => - ctx.runInfo.useCount(best.ref) += 1 - best - } - case Nil => - failedSearch + /** If `alt1` is also a search success, try to disambiguate as follows: + * - If alt2 is preferred over alt1, pick alt2, otherwise return an + * ambiguous implicits error. + */ + def disambiguate(alt1: SearchResult, alt2: SearchSuccess) = alt1 match { + case alt1: SearchSuccess => + val diff = compareCandidate(alt1, alt2.ref, alt2.level) + assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank` + if (diff < 0) alt2 + else + // numericValueTypeBreak(alt1, alt2) recoverWith + SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument)) + case _: SearchFailure => alt2 } - def ranking(cand: Candidate) = -ctx.runInfo.useCount(cand.ref) + /** Faced with an ambiguous implicits failure `fail`, try to find another + * alternative among `pending` that is strictly better than both ambiguous + * alternatives. If that fails, return `fail` + */ + def healAmbiguous(pending: List[Candidate], fail: SearchFailure) = { + val ambi = fail.reason.asInstanceOf[AmbiguousImplicits] + val newPending = pending.filter(cand => + compareCandidate(ambi.alt1, cand.ref, cand.level) < 0 && + compareCandidate(ambi.alt2, cand.ref, cand.level) < 0) + rank(newPending, fail, Nil).recoverWith(_ => fail) + } + + /** Try to find a best matching implicit term among all the candidates in `pending`. + * @param pending The list of candidates that remain to be tested + * @param found The result obtained from previously tried candidates + * @param rfailures A list of all failures from previously tried candidates in reverse order + * + * The scheme is to try candidates one-by-one. If a trial is successful: + * - if the query term is a `Not[T]` treat it a failure, + * - otherwise, if a previous search was also successful, handle the ambiguity + * in `disambiguate`, + * - otherwise, continue the search with all candidates that are not strictly + * worse than the succesful candidate. + * If a trial failed: + * - if the query term is a `Not[T]` treat it as a success, + * - otherwise, if the failure is an ambiguity, try to heal it (see @healAmbiguous) + * and return an ambiguous error otherwise. However, under Scala2 mode this is + * treated as a simple failure, with a warning that semantics will change. + * - otherwise add the failure to `rfailures` and continue testing the other candidates. + */ + def rank(pending: List[Candidate], found: SearchResult, rfailures: List[SearchFailure]): SearchResult = + pending match { + case cand :: remaining => + negateIfNot(tryImplicit(cand)) match { + case fail: SearchFailure => + if (fail.isAmbiguous) + if (ctx.scala2Mode) { + val result = rank(remaining, found, NoMatchingImplicitsFailure :: rfailures) + if (result.isSuccess) + warnAmbiguousNegation(fail.reason.asInstanceOf[AmbiguousImplicits]) + result + } + else healAmbiguous(remaining, fail) + else rank(remaining, found, fail :: rfailures) + case best: SearchSuccess => + if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) + best + else disambiguate(found, best) match { + case retained: SearchSuccess => + val newPending = + if (retained eq found) remaining + else remaining.filter(cand => + compareCandidate(retained, cand.ref, cand.level) <= 0) + rank(newPending, retained, rfailures) + case fail: SearchFailure => + healAmbiguous(remaining, fail) + } + } + case nil => + if (rfailures.isEmpty) found + else found.recoverWith(_ => rfailures.reverse.maxBy(_.tree.treeSize)) + } - /** Sort list of implicit references according to their popularity - * (# of times each was picked in current run). + def negateIfNot(result: SearchResult) = + if (isNot) + result match { + case _: SearchFailure => + SearchSuccess(ref(defn.Not_value), defn.Not_value.termRef, 0)( + ctx.typerState.fresh().setCommittable(true)) + case _: SearchSuccess => + NoMatchingImplicitsFailure + } + else result + + def warnAmbiguousNegation(ambi: AmbiguousImplicits) = + ctx.migrationWarning( + i"""Ambiguous implicits ${ambi.alt1.ref.symbol.showLocated} and ${ambi.alt2.ref.symbol.showLocated} + |seem to be used to implement a local failure in order to negate an implicit search. + |According to the new implicit resolution rules this is no longer possible; + |the search will fail with a global ambiguity error instead. + | + |Consider using the scala.implicits.Not class to implement similar functionality.""", pos) + + /** A relation that imfluences the order in which implicits are tried. + * We prefer (in order of importance) + * 1. more deeply nested definitions + * 2. definitions in subclasses + * 3. definitions with fewer implicit parameters + * The reason for (3) is that we want to fail fast if the search type + * is underconstrained. So we look for "small" goals first, because that + * will give an ambiguity quickly. + */ + def prefer(cand1: Candidate, cand2: Candidate): Boolean = { + val level1 = cand1.level + val level2 = cand2.level + if (level1 > level2) return true + if (level1 < level2) return false + val sym1 = cand1.ref.symbol + val sym2 = cand2.ref.symbol + val ownerScore = compareOwner(sym1.maybeOwner, sym2.maybeOwner) + if (ownerScore > 0) return true + if (ownerScore < 0) return false + val arity1 = sym1.info.firstParamTypes.length + val arity2 = sym2.info.firstParamTypes.length + if (arity1 < arity2) return true + if (arity1 > arity2) return false + false + } + + /** Sort list of implicit references according to `prefer`. + * This is just an optimization that aims at reducing the average + * number of candidates to be tested. */ def sort(eligible: List[Candidate]) = eligible match { case Nil => eligible case e1 :: Nil => eligible case e1 :: e2 :: Nil => - if (ranking(e2) < ranking(e1)) e2 :: e1 :: Nil + if (prefer(e2, e1)) e2 :: e1 :: Nil else eligible - case _ => eligible.sortBy(ranking) + case _ => + eligible.sortWith(prefer) } - condense(rankImplicits(sort(eligible), Nil)) - } + rank(sort(eligible), NoMatchingImplicitsFailure, Nil) + } // end searchImplicits /** Find a unique best implicit reference */ def bestImplicit(contextual: Boolean): SearchResult = { val eligible = if (contextual) ctx.implicits.eligible(wildProto) else implicitScope(wildProto).eligible - searchImplicits(eligible, contextual) match { - case result: SearchSuccess => result - case result: AmbiguousImplicits => result - case result: SearchFailure => - if (contextual) bestImplicit(contextual = false) else result + searchImplicits(eligible, contextual).recoverWith { + failure => failure.reason match { + case _: AmbiguousImplicits => failure + case _ => if (contextual) bestImplicit(contextual = false) else failure + } } } def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp, ctx) } - - final class ExplainedImplicitSearch(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context) - extends ImplicitSearch(pt, argument, pos) { - private[this] var myFailures = new mutable.ListBuffer[ExplainedSearchFailure] - private def record(fail: ExplainedSearchFailure) = { - myFailures += fail - fail - } - def failures = myFailures.toList - override def nonMatchingImplicit(ref: TermRef, trail: List[MessageContainer]) = - record(new NonMatchingImplicit(ref, pt, argument, trail)) - override def divergingImplicit(ref: TermRef) = - record(new DivergingImplicit(ref, pt, argument)) - override def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure = - record(new ShadowedImplicit(ref, shadowing, pt, argument)) - override def failedSearch: SearchFailure = { - //println(s"wildProto = $wildProto") - //println(s"implicit scope = ${implicitScope(wildProto).companionRefs}") - new FailedImplicit(failures, pt, argument) - } - } } /** Records the history of currently open implicit searches @@ -992,6 +1088,8 @@ class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { else updateMap(proto.classSymbols, seen) } } + + override def toString = s"SearchHistory(depth = $searchDepth, seen = $seen)" } /** A set of term references where equality is =:= */ diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 3768d4f26598..8298889a70f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -101,7 +101,7 @@ class ReTyper extends Typer { override def checkVariance(tree: Tree)(implicit ctx: Context) = () override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult = - Implicits.NoImplicitMatches + Implicits.NoMatchingImplicitsFailure override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = () override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e8da2e58b628..654c3fa53738 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -548,7 +548,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit require(ctx.mode.is(Mode.Pattern)) inferImplicit(defn.ClassTagType.appliedTo(tref), EmptyTree, tree.tpt.pos)(ctx.retractMode(Mode.Pattern)) match { - case SearchSuccess(clsTag, _, _, _) => + case SearchSuccess(clsTag, _, _) => typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt) case _ => tree @@ -2010,23 +2010,58 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val tvarsToInstantiate = tvarsInParams(tree) wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate)) val constr = ctx.typerState.constraint + + def dummyArg(tp: Type) = untpd.Ident(nme.???).withTypeUnchecked(tp) + def addImplicitArgs(implicit ctx: Context) = { - val errors = new mutable.ListBuffer[() => String] - def implicitArgError(msg: => String) = { - errors += (() => msg) - EmptyTree + def implicitArgs(formals: List[Type]): List[Tree] = formals match { + case Nil => Nil + case formal :: formals1 => + val arg = inferImplicitArg(formal, tree.pos.endPos) + arg.tpe match { + case failed: SearchFailureType + if !failed.isInstanceOf[AmbiguousImplicits] && !tree.symbol.hasDefaultParams => + // no need to search further, the adapt fails in any case + // the reason why we continue inferring arguments in case of an AmbiguousImplicits + // is that we need to know whether there are further errors. + // If there are none, we have to propagate the ambiguity to the caller. + arg :: formals1.map(dummyArg) + case _ => + arg :: implicitArgs(formals1) + } } - def issueErrors() = { - for (err <- errors) ctx.error(err(), tree.pos.endPos) - tree.withType(wtp.resultType) + val args = implicitArgs(wtp.paramInfos) + + def propagatedFailure(args: List[Tree]): Type = args match { + case arg :: args1 => + arg.tpe match { + case ambi: AmbiguousImplicits => + propagatedFailure(args1) match { + case NoType | (_: AmbiguousImplicits) => ambi + case failed => failed + } + case failed: SearchFailureType => failed + case _ => propagatedFailure(args1) + } + case Nil => NoType } - val args = (wtp.paramNames, wtp.paramInfos).zipped map { (pname, formal) => - def implicitArgError(msg: String => String) = - errors += (() => msg(em"parameter $pname of $methodStr")) - if (errors.nonEmpty) EmptyTree - else inferImplicitArg(formal, implicitArgError, tree.pos.endPos) + + val propFail = propagatedFailure(args) + + def issueErrors(): Tree = { + (wtp.paramNames, wtp.paramInfos, args).zipped.foreach { (paramName, formal, arg) => + arg.tpe match { + case failure: SearchFailureType => + ctx.error( + missingArgMsg(arg, formal, implicitParamString(paramName, methodStr, tree)), + tree.pos.endPos) + case _ => + } + } + untpd.Apply(tree, args).withType(propFail) } - if (errors.nonEmpty) { + + if (propFail.exists) { // If there are several arguments, some arguments might already // have influenced the context, binding variables, but later ones // might fail. In that case the constraint needs to be reset. @@ -2034,12 +2069,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // If method has default params, fall back to regular application // where all inferred implicits are passed as named args. - if (tree.symbol.hasDefaultParams) { + if (tree.symbol.hasDefaultParams && !propFail.isInstanceOf[AmbiguousImplicits]) { val namedArgs = (wtp.paramNames, args).zipped.flatMap { (pname, arg) => - arg match { - case EmptyTree => Nil - case _ => untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil - } + if (arg.tpe.isError) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil } tryEither { implicit ctx => typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt) @@ -2160,13 +2192,19 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) - val functionExpected = defn.isFunctionType(ptNorm) + lazy val functionExpected = defn.isFunctionType(ptNorm) + lazy val resultMatch = constrainResult(wtp, followAlias(pt)) wtp match { case wtp: ExprType => adaptInterpolated(tree.withType(wtp.resultType), pt) - case wtp: MethodType - if wtp.isImplicitMethod && (constrainResult(wtp, followAlias(pt)) || !functionExpected) => - adaptNoArgsImplicitMethod(wtp) + case wtp: MethodType if wtp.isImplicitMethod && (resultMatch || !functionExpected) => + if (resultMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) + else { + // Don't proceed with implicit search if result type cannot match - the search + // will likely be under-constrained, which means that an unbounded number of alternatives + // is tried. See strawman-contrib MapDecoratorTest.scala for an example where this happens. + err.typeMismatch(tree, pt) + } case wtp: MethodType if !pt.isInstanceOf[SingletonType] => val arity = if (functionExpected) @@ -2230,23 +2268,23 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } // try an implicit conversion val prevConstraint = ctx.typerState.constraint - def recover(failure: SearchFailure) = + def recover(failure: SearchFailureType) = if (isFullyDefined(wtp, force = ForceDegree.all) && ctx.typerState.constraint.ne(prevConstraint)) adapt(tree, pt) else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled)) inferView(tree, pt) match { - case SearchSuccess(inferred, _, _, _) => + case SearchSuccess(inferred, _, _) => adapt(inferred, pt)(ctx.retractMode(Mode.ImplicitsEnabled)) case failure: SearchFailure => - if (pt.isInstanceOf[ProtoType] && !failure.isInstanceOf[AmbiguousImplicits]) + if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) // don't report the failure but return the tree unchanged. This - // wil cause a failure at the next level out, which usually gives + // will cause a failure at the next level out, which usually gives // a better error message. tree - else recover(failure) + else recover(failure.reason) } - else recover(NoImplicitMatches) + else recover(NoMatchingImplicits) } def adaptType(tp: Type): Tree = { diff --git a/library/src/scala/implicits/Not.scala b/library/src/scala/implicits/Not.scala new file mode 100644 index 000000000000..6c06444145fc --- /dev/null +++ b/library/src/scala/implicits/Not.scala @@ -0,0 +1,45 @@ +package scala.implicits + +/** A special class used to implement negation in implicit search. + * + * Consider the problem of using implicit `i1` for a query type `D` if an implicit + * for some other class `C` is available, and using an implicit `i2` if no implicit + * value of type `C` is available. If we do not want to prioritize `i1` and `i2` by + * putting them in different traits we can instead define the following: + * + * implicit def i1: D(implicit ev: C) = ... + * implicit def i2: D(implicit ev: Not[C]) = ... + * + * `Not` is treated specially in implicit search, similar to the way logical negation + * is treated in Prolog: The implicit search for `Not[C]` succeeds if and only if the implicit + * search for `C` fails. + * + * In Scala 2 this form of negation can be simulated by setting up a conditional + * ambiguous implicit and an unconditional fallback, the way it is done with the + * `default`, `amb1` and `amb2` methods below. Due to the way these two methods are + * defined, `Not` is also usable from Scala 2. + * + * In Dotty, ambiguity is a global error, and therefore cannot be used to implement negation. + * Instead, `Not` is treated natively in implicit search. + */ +final class Not[+T] private () + +trait LowPriorityNot { + + /** A fallback method used to emulate negation in Scala 2 */ + implicit def default[T]: Not[T] = Not.value +} +object Not extends LowPriorityNot { + + /** A value of type `Not` to signal a successful search for `Not[C]` (i.e. a failing + * search for `C`). A reference to this value will be explicitly constructed by Dotty's + * implicit search algorithm + */ + def value: Not[Nothing] = new Not[Nothing]() + + /** One of two ambiguous methods used to emulate negation in Scala 2 */ + implicit def amb1[T](implicit ev: T): Not[T] = ??? + + /** One of two ambiguous methods used to emulate negation in Scala 2 */ + implicit def amb2[T](implicit ev: T): Not[T] = ??? +} diff --git a/tests/neg/i3430.scala b/tests/neg/i3430.scala new file mode 100644 index 000000000000..5e88377dfdde --- /dev/null +++ b/tests/neg/i3430.scala @@ -0,0 +1,5 @@ +object Test extends App { + + println(Nil.min) // error: no implicit found + +} diff --git a/tests/neg/implicitSearch.scala b/tests/neg/implicitSearch.scala index bcb12c3e9a7c..a5be79474ffd 100644 --- a/tests/neg/implicitSearch.scala +++ b/tests/neg/implicitSearch.scala @@ -12,7 +12,7 @@ object Test { sort(xs) def f[T](xs: List[List[List[T]]]) = - sort(xs) // error TODO: improve the -explain-implicts diagnostic + sort(xs) // error (with a partially constructed implicit argument shown) listOrd(listOrd(implicitly[Ord[T]] /*not found*/)) // error } diff --git a/tests/pos-scala2/i3396.scala b/tests/pos-scala2/i3396.scala new file mode 100644 index 000000000000..5cf3ccba8980 --- /dev/null +++ b/tests/pos-scala2/i3396.scala @@ -0,0 +1,34 @@ +object Test { + + trait Tagged[A] + + // Negation Tagged: NotTagged[A] is available only if there are no Tagged[A] in scope. + trait NotTagged[A] + trait NotTaggedLowPrio { + implicit def notTaggedInstance[A]: NotTagged[A] = null + } + object NotTagged extends NotTaggedLowPrio { + implicit def notTaggedAmbiguity1[A](implicit ev: Tagged[A]): NotTagged[A] = null + implicit def notTaggedAmbiguity2[A](implicit ev: Tagged[A]): NotTagged[A] = null + } + + + case class Foo[A](value: Boolean) + trait FooLowPrio { + implicit def fooDefault[A]: Foo[A] = Foo(true) + } + object Foo extends FooLowPrio { + implicit def fooNotTagged[A](implicit ev: NotTagged[A]): Foo[A] = Foo(false) + } + + + def main(args: Array[String]): Unit = { + implicit val taggedInt: Tagged[Int] = null + + assert(implicitly[Foo[Int]].value) // fooDefault + + assert(!implicitly[Foo[String]].value) // fooNotTagged + + println(1) + } +} diff --git a/tests/pos/implicit-divergent.scala b/tests/pos/implicit-divergent.scala new file mode 100644 index 000000000000..91b561be8bc6 --- /dev/null +++ b/tests/pos/implicit-divergent.scala @@ -0,0 +1,12 @@ +class Foo[T] +object Foo { + implicit def intFoo: Foo[Int] = ??? +} + +object Test { + implicit def genFoo[T](implicit f: Foo[T => T]): Foo[T] = ??? + + def test: Unit = { + implicitly[Foo[Int]] + } +} diff --git a/tests/run/config.check b/tests/run/config.check new file mode 100644 index 000000000000..b20cb76502a7 --- /dev/null +++ b/tests/run/config.check @@ -0,0 +1,2 @@ +Some(Person(Name(John,Doe),Age(20))) +None diff --git a/tests/run/config.scala b/tests/run/config.scala new file mode 100644 index 000000000000..8caa7c2f771e --- /dev/null +++ b/tests/run/config.scala @@ -0,0 +1,123 @@ +case class Name(first: String, last: String) +case class Age(age: Int) +case class Person(name: Name, age: Age) +case class Config(name: String, age: Int) + + + +object Imperative { + import Configs._ + import Exceptions._ + + // Algebraic Effects + + def readName: Possibly[Configured[Name]] = { + val parts = config.name.split(" ") + require(parts.length >= 2) + Name(parts(0), parts.tail.mkString(" ")) + } + + def readAge: Configured[Possibly[Age]] = { + val age = config.age + require(1 <= age && age <= 150) + Age(age) + } + + def readPerson: Configured[Option[Person]] = + attempt( + Some(Person(readName, readAge)) + ).onError(None) + + def main(args: Array[String]) = { + println(readPerson(Config("John Doe", 20))) + println(readPerson(Config("Incognito", 99))) + } +} + +object Configs { + type Configured[T] = implicit Config => T + def config: Configured[Config] = implicitly[Config] +} + +object Exceptions { + + private class E extends Exception + + class CanThrow private[Exceptions] () { + private[Exceptions] def throwE() = throw new E + } + + type Possibly[T] = implicit CanThrow => T + + def require(p: Boolean)(implicit ct: CanThrow): Unit = + if (!p) ct.throwE() + + def attempt[T](op: Possibly[T]) = new OnError(op) + + class OnError[T](op: Possibly[T]) { + def onError(fallback: => T): T = + try op(new CanThrow) + catch { case ex: E => fallback } + } +} + +object Test extends App { + import Configs._ + import Exceptions._ + + type PC[T] = Possibly[Configured[T]] + + val names: PC[List[Name]] = readName :: Nil + val firstNames: PC[List[String]] = names.map(_.first) + val longest: PC[String] = firstNames.maxBy(_.length) + + def readName: Configured[Possibly[Name]] = { + val parts = config.name.split(" ") + require(parts.length >= 2) + Name(parts(0), parts.tail.mkString(" ")) + } + + def readAge: Possibly[Configured[Age]] = { + val age = config.age + require(1 <= age && age <= 150) + Age(age) + } + + def readPerson: Configured[Option[Person]] = + attempt( + Some(Person(readName, readAge)) + ).onError(None) + + val config1 = Config("John Doe", 20) + val config2 = Config("Incognito", 99) + + println(readPerson(config1)) + println(readPerson(config2)) +} + +object OptionTest extends App { + + def readName(config: Config): Option[Name] = { + val parts = config.name.split(" ") + if (parts.length >= 2) Some(Name(parts(0), parts.tail.mkString(" "))) + else None + } + + def readAge(config: Config): Option[Age] = { + val age = config.age + if (1 <= age && age <= 150) Some(Age(age)) else None + } + + def readPerson(config: Config): Option[Person] = + for { + name <- readName(config) + age <- readAge(config) + } + yield Person(name, age) + + val config1 = Config("John Doe", 20) + val config2 = Config("Incognito", 99) + + println(readPerson(config1)) + println(readPerson(config2)) +} diff --git a/tests/run/i3396.scala b/tests/run/i3396.scala new file mode 100644 index 000000000000..fec007591d7a --- /dev/null +++ b/tests/run/i3396.scala @@ -0,0 +1,25 @@ +import implicits.Not + +object Test { + + trait Tagged[A] + + case class Foo[A](value: Boolean) + trait FooLowPrio { + implicit def fooDefault[A]: Foo[A] = Foo(true) + } + object Foo extends FooLowPrio { + implicit def fooNotTagged[A](implicit ev: Not[Tagged[A]]): Foo[A] = Foo(false) + } + + + def main(args: Array[String]): Unit = { + implicit val taggedInt: Tagged[Int] = null + + assert(implicitly[Foo[Int]].value) // fooDefault + + assert(!implicitly[Foo[String]].value) // fooNotTagged + + println(1) + } +} diff --git a/tests/run/iterator-from.scala b/tests/run/iterator-from.scala index c7c0f9809cc5..1f73a7ffe8fb 100644 --- a/tests/run/iterator-from.scala +++ b/tests/run/iterator-from.scala @@ -63,7 +63,8 @@ object Test extends dotty.runtime.LegacyApp { testSet(immutable.TreeSet(keys:_*), keys) testSet(mutable.TreeSet(keys:_*), keys) val days = keys map {n => Weekday(n % Weekday.values.size)} - testSet(Weekday.ValueSet(days:_*), days) + + testSet(Weekday.ValueSet(days:_*), days) // Note: produces divergent search in scalac val treeMap = immutable.TreeMap(keyValues:_*) testMap(treeMap, keyValues)