Skip to content

Commit d7c8604

Browse files
committed
Propagate implicit ambiguity and divergence failures
Propagate implicit ambiguity and divergence failures from implicit arguments to their callers. In particular, and ambiguity in an implicit argument means that the whole implicit search is ambiguous. Previously, the local ambiguity translated to an "implicit not found" on the next outer level, which meant that another implicit on that level could qualify and the ambiguity would be forgotten. The code is still a bit rough and could profit from some refactorings.
1 parent 2d4efa0 commit d7c8604

File tree

5 files changed

+147
-83
lines changed

5 files changed

+147
-83
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
503503
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)
504504
case _: NonMatchingImplicit | NoImplicitMatches =>
505505
"NoImplicitMatches"
506-
case _: DivergingImplicit | DivergingImplicit =>
506+
case _: DivergingImplicit =>
507507
"Diverging Implicit"
508508
case result: ShadowedImplicit =>
509509
"Shadowed Implicit"

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,8 @@ trait Dynamic { self: Typer with Applications =>
150150
fail(i"""takes too many parameters.
151151
|Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
152152
else {
153-
def issueError(msgFn: String => String): Unit = ctx.error(msgFn(""), tree.pos)
154153
val ctags = tpe.paramInfos.map(pt =>
155-
inferImplicitArg(defn.ClassTagType.appliedTo(pt :: Nil), issueError, tree.pos.endPos))
154+
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos))
156155
structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType())
157156
}
158157
case tpe: ValueType =>

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

Lines changed: 95 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,11 @@ object Implicits {
278278
/** A "no matching implicit found" failure */
279279
case object NoImplicitMatches extends SearchFailure
280280

281-
case object DivergingImplicit extends SearchFailure
281+
/** A tree representing a failed search for an implicit argument */
282+
case class FailedSearch(failure: SearchFailure, errorFn: String => String) extends WithoutTypeOrPos[Type] {
283+
override def tag = -1
284+
override def isEmpty = true
285+
}
282286

283287
/** A search failure that can show information about the cause */
284288
abstract class ExplainedSearchFailure extends SearchFailure {
@@ -552,7 +556,7 @@ trait Implicits { self: Typer =>
552556
* which is itself parameterized by another string,
553557
* indicating where the implicit parameter is needed
554558
*/
555-
def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = {
559+
def inferImplicitArg(formal: Type, pos: Position)(implicit ctx: Context): Tree = {
556560

557561
/** If `formal` is of the form ClassTag[T], where `T` is a class type,
558562
* synthesize a class tag for `T`.
@@ -562,16 +566,18 @@ trait Implicits { self: Typer =>
562566
case arg :: Nil =>
563567
fullyDefinedType(arg, "ClassTag argument", pos) match {
564568
case defn.ArrayOf(elemTp) =>
565-
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos)
569+
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), pos)
566570
if (etag.isEmpty) etag else etag.select(nme.wrap)
567571
case tp if hasStableErasure(tp) =>
568572
if (defn.isBottomClass(tp.typeSymbol))
569-
error(where => i"attempt to take ClassTag of undetermined type for $where")
570-
ref(defn.ClassTagModule)
571-
.select(nme.apply)
572-
.appliedToType(tp)
573-
.appliedTo(clsOf(erasure(tp)))
574-
.withPos(pos)
573+
FailedSearch(NoImplicitMatches,
574+
where => i"attempt to take ClassTag of undetermined type for $where")
575+
else
576+
ref(defn.ClassTagModule)
577+
.select(nme.apply)
578+
.appliedToType(tp)
579+
.appliedTo(clsOf(erasure(tp)))
580+
.withPos(pos)
575581
case tp =>
576582
EmptyTree
577583
}
@@ -635,17 +641,16 @@ trait Implicits { self: Typer =>
635641
else
636642
arg
637643
case ambi: AmbiguousImplicits =>
638-
error(where => s"ambiguous implicits: ${ambi.explanation} of $where")
639-
EmptyTree
644+
FailedSearch(ambi, where => s"ambiguous implicits: ${ambi.explanation} of $where")
640645
case failure: SearchFailure =>
641-
val arg =
646+
val fallbackArg =
642647
if (formalValue.isRef(defn.ClassTagClass))
643648
synthesizedClassTag(formalValue)
644649
else if (formalValue.isRef(defn.EqClass))
645650
synthesizedEq(formalValue)
646651
else
647652
EmptyTree
648-
if (!arg.isEmpty) arg
653+
if (!fallbackArg.isEmpty) fallbackArg
649654
else {
650655
var msgFn = (where: String) =>
651656
em"no implicit argument of type $formal found for $where" + failure.postscript
@@ -659,12 +664,21 @@ trait Implicits { self: Typer =>
659664
formalValue.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
660665
formalValue.argInfos)
661666
}
662-
error(msgFn)
663-
EmptyTree
667+
FailedSearch(failure, msgFn)
664668
}
665669
}
666670
}
667671

672+
/** Search an implicit argument and report error if not found */
673+
def implicitArgTree(formal: Type, pos: Position, where: String = "")(implicit ctx: Context): Tree =
674+
inferImplicitArg(formal, pos) match {
675+
case FailedSearch(fail, errorFn) =>
676+
ctx.error(errorFn(where), pos)
677+
EmptyTree
678+
case tree =>
679+
tree
680+
}
681+
668682
private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = {
669683
def eqNullable: Boolean = {
670684
val other =
@@ -694,8 +708,7 @@ trait Implicits { self: Typer =>
694708
/** Check that equality tests between types `ltp` and `rtp` make sense */
695709
def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit =
696710
if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) {
697-
val res = inferImplicitArg(
698-
defn.EqType.appliedTo(ltp, rtp), msgFun => ctx.error(msgFun(""), pos), pos)
711+
val res = implicitArgTree(defn.EqType.appliedTo(ltp, rtp), pos)
699712
implicits.println(i"Eq witness found for $ltp / $rtp: $res: ${res.tpe}")
700713
}
701714

@@ -771,7 +784,10 @@ trait Implicits { self: Typer =>
771784

772785
/** Search failures; overridden in ExplainedImplicitSearch */
773786
protected def nonMatchingImplicit(ref: TermRef, trail: List[MessageContainer]): SearchFailure = NoImplicitMatches
774-
protected def divergingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches
787+
protected def divergingImplicit(ref: TermRef): SearchFailure = {
788+
implicits.println(i"diverging implicit: $ref for $pt, search history = ${ctx.searchHistory}")
789+
new DivergingImplicit(ref, pt, argument)
790+
}
775791
protected def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure = NoImplicitMatches
776792
protected def failedSearch: SearchFailure = NoImplicitMatches
777793

@@ -802,11 +818,16 @@ trait Implicits { self: Typer =>
802818
}
803819
}
804820

805-
if (ctx.reporter.hasErrors)
806-
nonMatchingImplicit(ref, ctx.reporter.removeBufferedMessages)
821+
if (ctx.reporter.hasErrors) {
822+
val trail = ctx.reporter.removeBufferedMessages
823+
generated1 match {
824+
case failed: FailedSearch => failed.failure
825+
case _ => nonMatchingImplicit(ref, trail)
826+
}
827+
}
807828
else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) &&
808829
!shadowing.tpe.isError && !refSameAs(shadowing)) {
809-
implicits.println(i"SHADOWING $ref in ${ref.termSymbol.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}")
830+
implicits.println(i"SHADOWING $ref in ${ref.termSymbol.maybeOwner} is shadowed by $shadowing in ${shadowing.symbol.maybeOwner}")
810831
shadowedImplicit(ref, methPart(shadowing).tpe)
811832
}
812833
else
@@ -815,28 +836,38 @@ trait Implicits { self: Typer =>
815836

816837
/** Given a list of implicit references, produce a list of all implicit search successes,
817838
* where the first is supposed to be the best one.
839+
* Except if one of the results is a diverging implicit, produce a list consisting
840+
* of just that result.
818841
* @param pending The list of implicit references that remain to be investigated
819842
* @param acc An accumulator of successful matches found so far.
820843
*/
821-
def rankImplicits(pending: List[Candidate], acc: List[SearchSuccess]): List[SearchSuccess] = pending match {
822-
case cand :: pending1 =>
823-
val history = ctx.searchHistory nest wildProto
824-
val result =
825-
if (history eq ctx.searchHistory) divergingImplicit(cand.ref)
826-
else typedImplicit(cand)(nestedContext.setNewTyperState().setSearchHistory(history))
827-
result match {
828-
case fail: SearchFailure =>
829-
rankImplicits(pending1, acc)
830-
case best: SearchSuccess =>
831-
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) best :: Nil
832-
else {
833-
val newPending = pending1.filter(cand1 =>
834-
ctx.typerState.test(isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext)))
835-
rankImplicits(newPending, best :: acc)
836-
}
837-
}
838-
case nil => acc
839-
}
844+
def rankImplicits(pending: List[Candidate],
845+
successes: List[SearchSuccess],
846+
failures: mutable.ListBuffer[SearchFailure]): (List[SearchSuccess], List[SearchFailure]) =
847+
pending match {
848+
case cand :: pending1 =>
849+
val history = ctx.searchHistory nest wildProto
850+
val result =
851+
if (history eq ctx.searchHistory) divergingImplicit(cand.ref)
852+
else typedImplicit(cand)(nestedContext.setNewTyperState().setSearchHistory(history))
853+
result match {
854+
case fail: AmbiguousImplicits =>
855+
(Nil, fail :: Nil)
856+
case fail: SearchFailure =>
857+
rankImplicits(pending1, successes,
858+
if (fail.isInstanceOf[DivergingImplicit]) fail +=: failures
859+
else failures += fail)
860+
case best: SearchSuccess =>
861+
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
862+
(best :: Nil, failures.toList)
863+
else {
864+
val newPending = pending1.filter(cand1 =>
865+
ctx.typerState.test(isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext)))
866+
rankImplicits(newPending, best :: successes, failures)
867+
}
868+
}
869+
case nil => (successes, failures.toList)
870+
}
840871

841872
/** If the (result types of) the expected type, and both alternatives
842873
* are all numeric value types, return the alternative which has
@@ -858,26 +889,28 @@ trait Implicits { self: Typer =>
858889
}
859890

860891
/** Convert a (possibly empty) list of search successes into a single search result */
861-
def condense(hits: List[SearchSuccess]): SearchResult = hits match {
862-
case best :: alts =>
892+
def condense(successes: List[SearchSuccess], failures: List[SearchFailure]): SearchResult = successes match {
893+
case (best: SearchSuccess) :: (alts: List[SearchSuccess] @ unchecked) =>
863894
alts.find(alt =>
864895
ctx.typerState.test(isAsGood(alt.ref, best.ref, alt.level, best.level))) match {
865-
case Some(alt) =>
866-
typr.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}")
867-
/* !!! DEBUG
868-
println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}")
869-
isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState)
870-
*/
871-
numericValueTieBreak(best, alt) match {
872-
case eliminated: SearchSuccess => condense(hits.filter(_ ne eliminated))
873-
case _ => new AmbiguousImplicits(best.ref, alt.ref, pt, argument)
874-
}
875-
case None =>
876-
ctx.runInfo.useCount(best.ref) += 1
877-
best
878-
}
896+
case Some(alt) =>
897+
implicits.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}")
898+
/* !!! DEBUG
899+
println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}")
900+
isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState)
901+
*/
902+
numericValueTieBreak(best, alt) match {
903+
case eliminated: SearchSuccess =>
904+
condense(successes.filter(_ ne eliminated), failures)
905+
case _ =>
906+
new AmbiguousImplicits(best.ref, alt.ref, pt, argument)
907+
}
908+
case None =>
909+
ctx.runInfo.useCount(best.ref) += 1
910+
best
911+
}
879912
case Nil =>
880-
failedSearch
913+
failures.headOption.getOrElse(NoImplicitMatches)
881914
}
882915

883916
def ranking(cand: Candidate) = -ctx.runInfo.useCount(cand.ref)
@@ -894,7 +927,8 @@ trait Implicits { self: Typer =>
894927
case _ => eligible.sortBy(ranking)
895928
}
896929

897-
condense(rankImplicits(sort(eligible), Nil))
930+
val (successes, failures) = rankImplicits(sort(eligible), Nil, new mutable.ListBuffer)
931+
condense(successes, failures)
898932
}
899933

900934
/** Find a unique best implicit reference */
@@ -905,6 +939,7 @@ trait Implicits { self: Typer =>
905939
searchImplicits(eligible, contextual) match {
906940
case result: SearchSuccess => result
907941
case result: AmbiguousImplicits => result
942+
case result: DivergingImplicit => result
908943
case result: SearchFailure =>
909944
if (contextual) bestImplicit(contextual = false) else result
910945
}
@@ -994,6 +1029,8 @@ class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) {
9941029
else updateMap(proto.classSymbols, seen)
9951030
}
9961031
}
1032+
1033+
override def toString = s"SearchHistory(depth = $searchDepth, seen = $seen)"
9971034
}
9981035

9991036
/** A set of term references where equality is =:= */

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

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,35 +2001,60 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
20012001
wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate))
20022002
val constr = ctx.typerState.constraint
20032003
def addImplicitArgs(implicit ctx: Context) = {
2004-
val errors = new mutable.ListBuffer[() => String]
2005-
def implicitArgError(msg: => String) = {
2006-
errors += (() => msg)
2007-
EmptyTree
2008-
}
2009-
def issueErrors() = {
2010-
for (err <- errors) ctx.error(err(), tree.pos.endPos)
2011-
tree.withType(wtp.resultType)
2012-
}
2013-
val args = (wtp.paramNames, wtp.paramInfos).zipped map { (pname, formal) =>
2014-
def implicitArgError(msg: String => String) =
2015-
errors += (() => msg(em"parameter $pname of $methodStr"))
2016-
if (errors.nonEmpty) EmptyTree
2017-
else inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
2004+
def implicitArgs(formals: List[Type]): List[Tree] = formals match {
2005+
case Nil => Nil
2006+
case formal :: formals1 =>
2007+
inferImplicitArg(formal, tree.pos.endPos) match {
2008+
case arg: FailedSearch
2009+
if !arg.failure.isInstanceOf[AmbiguousImplicits] && !tree.symbol.hasDefaultParams =>
2010+
// no need to search further, the adapt fails in any case
2011+
// the reason why we continue inferring in case of an AmbiguousImplicits
2012+
// is that we need to know whether there are further errors.
2013+
// If there are none, we have to propagate the ambiguity to the caller.
2014+
arg :: Nil
2015+
case arg =>
2016+
arg :: implicitArgs(formals1)
2017+
}
20182018
}
2019-
if (errors.nonEmpty) {
2019+
val args = implicitArgs(wtp.paramInfos)
2020+
2021+
val failedArgs = new mutable.ListBuffer[Tree]
2022+
val ambiguousArgs = new mutable.ListBuffer[Tree]
2023+
for (arg @ FailedSearch(failure, _) <- args)
2024+
(if (failure.isInstanceOf[AmbiguousImplicits]) ambiguousArgs else failedArgs) += arg
2025+
2026+
if (ambiguousArgs.isEmpty && failedArgs.isEmpty)
2027+
adapt(tpd.Apply(tree, args), pt)
2028+
else {
20202029
// If there are several arguments, some arguments might already
20212030
// have influenced the context, binding variables, but later ones
20222031
// might fail. In that case the constraint needs to be reset.
20232032
ctx.typerState.constraint = constr
20242033

2034+
def issueErrors() = {
2035+
def recur(paramNames: List[TermName], remainingArgs: List[Tree]): Tree = remainingArgs match {
2036+
case Nil =>
2037+
tree.withType(wtp.resultType)
2038+
case (arg @ FailedSearch(failure, msgFn)) :: args1 =>
2039+
val msg = msgFn(em"parameter ${paramNames.head} of $methodStr")
2040+
def closedArg = FailedSearch(failure, _ => msg)
2041+
ctx.error(msg, tree.pos.endPos)
2042+
failure match {
2043+
case _: AmbiguousImplicits if failedArgs.isEmpty => closedArg // propagate
2044+
case _: DivergingImplicit => closedArg // propagate
2045+
case _ => tree.withType(wtp.resultType) // show error at enclosing level instead
2046+
}
2047+
case arg :: args1 =>
2048+
recur(paramNames.tail, args1)
2049+
}
2050+
recur(wtp.paramNames, args)
2051+
}
2052+
20252053
// If method has default params, fall back to regular application
20262054
// where all inferred implicits are passed as named args.
2027-
if (tree.symbol.hasDefaultParams) {
2055+
if (failedArgs.nonEmpty && tree.symbol.hasDefaultParams) {
20282056
val namedArgs = (wtp.paramNames, args).zipped.flatMap { (pname, arg) =>
2029-
arg match {
2030-
case EmptyTree => Nil
2031-
case _ => untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil
2032-
}
2057+
if (arg.isEmpty) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil
20332058
}
20342059
tryEither { implicit ctx =>
20352060
typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt)
@@ -2038,7 +2063,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
20382063
}
20392064
} else issueErrors()
20402065
}
2041-
else adapt(tpd.Apply(tree, args), pt)
20422066
}
20432067
addImplicitArgs(argCtx(tree))
20442068
}

tests/run/iterator-from.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ object Test extends dotty.runtime.LegacyApp {
6363
testSet(immutable.TreeSet(keys:_*), keys)
6464
testSet(mutable.TreeSet(keys:_*), keys)
6565
val days = keys map {n => Weekday(n % Weekday.values.size)}
66-
testSet(Weekday.ValueSet(days:_*), days)
66+
67+
// The following does not work with the implicit change that propagates nested ambiguous errors to the top.
68+
// We get ambiguous implicits because Predef.$conforms and convertIfView both fit WeekDay.Value => Ordered[WeekDay.Value].
69+
// It does not work in scalac either; there we get a divergent implicit.
70+
// testSet(Weekday.ValueSet(days:_*), days)
6771

6872
val treeMap = immutable.TreeMap(keyValues:_*)
6973
testMap(treeMap, keyValues)

0 commit comments

Comments
 (0)