Skip to content

Commit 2270899

Browse files
committed
New scheme for handling implicits
- Merge `rankImplicits` and `condense` into one phase - Fix handling of ambiguities - the previous unconditional abort was wrong, because another alternative might be better than both ambiguous choices.
1 parent d8028de commit 2270899

File tree

2 files changed

+27
-280
lines changed

2 files changed

+27
-280
lines changed

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

Lines changed: 27 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,9 @@ object Implicits {
257257
sealed abstract class SearchResult extends Showable {
258258
def tree: tpd.Tree
259259
def toText(printer: Printer): Text = printer.toText(this)
260-
def orElse(other: => SearchResult) = this match {
260+
def recoverWith(other: SearchFailure => SearchResult) = this match {
261261
case _: SearchSuccess => this
262-
case _ => other
262+
case fail: SearchFailure => other(fail)
263263
}
264264
}
265265

@@ -484,14 +484,8 @@ trait ImplicitRunInfo { self: RunInfo =>
484484
iscope(rootTp)
485485
}
486486

487-
/** A map that counts the number of times an implicit ref was picked */
488-
val useCount = new mutable.HashMap[TermRef, Int] {
489-
override def default(key: TermRef) = 0
490-
}
491-
492487
def clear() = {
493488
implicitScopeCache.clear()
494-
useCount.clear()
495489
}
496490
}
497491

@@ -836,12 +830,6 @@ trait Implicits { self: Typer =>
836830
typedImplicit(cand)(nestedContext().setNewTyperState().setSearchHistory(history))
837831
}
838832

839-
def tryInOrder(cand1: Candidate, cand2: Candidate) =
840-
tryImplicit(cand1) match {
841-
case success: SearchSuccess => success
842-
case _ => tryImplicit(cand2)
843-
}
844-
845833
def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int =
846834
if (prev.ref eq ref) 0
847835
else ctx.typerState.test(compare(prev.ref, ref, prev.level, level)(nestedContext()))
@@ -866,6 +854,14 @@ trait Implicits { self: Typer =>
866854
case _: SearchFailure => alt2
867855
}
868856

857+
def healAmbiguous(pending: List[Candidate], fail: SearchFailure) = {
858+
val ambi = fail.reason.asInstanceOf[AmbiguousImplicits]
859+
val newPending = pending.filter(cand =>
860+
compareCandidate(ambi.alt1, cand.ref, cand.level) < 0 &&
861+
compareCandidate(ambi.alt2, cand.ref, cand.level) < 0)
862+
rank(newPending, fail, Nil).recoverWith(_ => fail)
863+
}
864+
869865
def rank(pending: List[Candidate], found: SearchResult, rfailures: List[SearchFailure]): SearchResult = pending match {
870866
case cand :: pending1 =>
871867
tryImplicit(cand) match {
@@ -888,164 +884,17 @@ trait Implicits { self: Typer =>
888884
}
889885
case nil =>
890886
if (rfailures.isEmpty) found
891-
else found.orElse(rfailures.reverse.maxBy(_.tree.treeSize))
892-
}
893-
894-
def healAmbiguous(pending: List[Candidate], fail: SearchFailure) = {
895-
val ambi = fail.reason.asInstanceOf[AmbiguousImplicits]
896-
val newPending = pending.filter(cand =>
897-
compareCandidate(ambi.alt1, cand.ref, cand.level) < 0 &&
898-
compareCandidate(ambi.alt2, cand.ref, cand.level) < 0)
899-
rank(newPending, fail, Nil).orElse(fail)
900-
}
901-
902-
def go: SearchResult = eligible match {
903-
case Nil =>
904-
SearchFailure(new NoMatchingImplicits(pt, argument))
905-
case cand :: Nil =>
906-
tryImplicit(cand)
907-
case cand1 :: cand2 :: Nil =>
908-
def compareTwo = cmpCandidates(cand1, cand2) match {
909-
case 1 => tryInOrder(cand1, cand2)
910-
case -1 => tryInOrder(cand2, cand1)
911-
case 0 =>
912-
val alt1 = tryImplicit(cand1)
913-
val alt2 = tryImplicit(cand2)
914-
alt2 match {
915-
case alt2: SearchSuccess => disambiguate(alt1, alt2)
916-
case _ =>
917-
alt1 match {
918-
case alt1: SearchSuccess => alt1
919-
case _ => if (alt1.tree.treeSize < alt2.tree.treeSize) alt2 else alt1
920-
}
921-
}
922-
}
923-
compareTwo
924-
case _ =>
925-
val cands = eligible.toArray
926-
val pg = new PriorityGraph(cands, cmpCandidates)
927-
928-
def loop(found: SearchResult, rfailures: List[SearchFailure]): SearchResult =
929-
if (pg.hasNextSource())
930-
tryImplicit(cands(pg.nextSource())) match {
931-
case fail: SearchFailure =>
932-
if (fail.isAmbiguous) fail
933-
else {
934-
pg.dropLastSource()
935-
loop(found, fail :: rfailures)
936-
}
937-
case best: SearchSuccess =>
938-
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
939-
best
940-
else disambiguate(found, best) match {
941-
case retained: SearchSuccess => loop(retained, rfailures)
942-
case ambi => ambi
943-
}
944-
}
945-
else if (found.isInstanceOf[SearchSuccess]) found
946-
else rfailures.reverse.maxBy(_.tree.treeSize)
947-
948-
loop(NoMatchingImplicitsFailure, Nil)
949-
}
950-
951-
/** Given a list of implicit references, produce a list of search results,
952-
* which is either a list of successes or a list of failures.
953-
* - if one of the references produces an ambiguity error, return it
954-
* as only element
955-
* - if some of the references produce successful searches, return those that do
956-
* - otherwise return a list of all failures
957-
*
958-
* If mode is ImplicitExploration or we assume coherence, stop at first
959-
* succesfull search.
960-
*
961-
* @param pending The list of implicit references that remain to be investigated
962-
*/
963-
def rankImplicits(pending: List[Candidate],
964-
successes: List[SearchSuccess],
965-
rfailures: List[SearchFailure]): List[SearchResult] =
966-
pending match {
967-
case cand :: pending1 =>
968-
val history = ctx.searchHistory nest wildProto
969-
val result =
970-
if (history eq ctx.searchHistory)
971-
SearchFailure(new DivergingImplicit(cand.ref, pt, argument))
972-
else
973-
typedImplicit(cand)(nestedContext().setNewTyperState().setSearchHistory(history))
974-
result match {
975-
case fail: SearchFailure =>
976-
if (fail.isAmbiguous) fail :: Nil
977-
else rankImplicits(pending1, successes, fail :: rfailures)
978-
case best: SearchSuccess =>
979-
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
980-
best :: Nil
981-
else {
982-
val newPending = pending1.filter(cand1 =>
983-
ctx.typerState.test(isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext())))
984-
rankImplicits(newPending, best :: successes, rfailures)
985-
}
986-
}
987-
case nil =>
988-
if (successes.nonEmpty) successes else rfailures.reverse
989-
}
990-
991-
/** If the (result types of) the expected type, and both alternatives
992-
* are all numeric value types, return the alternative which has
993-
* the smaller numeric subtype as result type, if it exists.
994-
* (This alternative is then discarded).
995-
*/
996-
def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess): SearchResult = {
997-
def isNumeric(tp: Type) = tp.typeSymbol.isNumericValueClass
998-
def isProperSubType(tp1: Type, tp2: Type) =
999-
tp1.isValueSubType(tp2) && !tp2.isValueSubType(tp1)
1000-
val rpt = pt.resultType
1001-
val rt1 = alt1.ref.widen.resultType
1002-
val rt2 = alt2.ref.widen.resultType
1003-
if (isNumeric(rpt) && isNumeric(rt1) && isNumeric(rt2))
1004-
if (isProperSubType(rt1, rt2)) alt1
1005-
else if (isProperSubType(rt2, rt1)) alt2
1006-
else NoMatchingImplicitsFailure
1007-
else NoMatchingImplicitsFailure
887+
else found.recoverWith(_ => rfailures.reverse.maxBy(_.tree.treeSize))
1008888
}
1009889

1010-
/** Convert a (possibly empty) list of search results into a single search result
1011-
* - if the list consists of one or more successes
1012-
* - if a following success is as good as the first one, issue an ambiguity error
1013-
* - otherwise return the first success
1014-
* - if the list consists of one or more failues, pick the failure with the largest
1015-
* associated tree.
1016-
* - if the list is empty, issue a "no matching implicits" error.
1017-
*/
1018-
def condense(results: List[SearchResult]): SearchResult = results match {
1019-
case (best: SearchSuccess) :: (alts: List[SearchSuccess] @ unchecked) =>
1020-
alts.find(alt =>
1021-
ctx.typerState.test(isAsGood(alt.ref, best.ref, alt.level, best.level))) match {
1022-
case Some(alt) =>
1023-
implicits.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}")
1024-
/* !!! DEBUG
1025-
println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}")
1026-
isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState)
1027-
*/
1028-
numericValueTieBreak(best, alt) match {
1029-
case eliminated: SearchSuccess =>
1030-
condense(results.filter(_ ne eliminated))
1031-
case _ =>
1032-
SearchFailure(new AmbiguousImplicits(best, alt, pt, argument))
1033-
}
1034-
case None =>
1035-
ctx.runInfo.useCount(best.ref) += 1
1036-
best
1037-
}
1038-
case (fail: SearchFailure) :: _ =>
1039-
results.maxBy(_.tree.treeSize)
1040-
case Nil =>
1041-
SearchFailure(new NoMatchingImplicits(pt, argument))
1042-
}
1043-
1044-
def ranking(cand: Candidate) = -ctx.runInfo.useCount(cand.ref)
1045-
1046-
/** Prefer `cand1` over `cand2` if they are in the same compilation unit
1047-
* and `cand1` is defined before `cand2`, or they are in different units and
1048-
* `cand1` has been selected as an implicit more often than `cand2`.
890+
/** A relation that imfluences the order in which implicits are tried.
891+
* We prefer (in order of importance)
892+
* 1. more deeply nested definitions
893+
* 2. definitions in subclasses
894+
* 3. definitions with fewer implicit parameters
895+
* The reason for (3) is that we want to fail fast if the search type
896+
* is underconstrained. So we look for "small" goals first, because that
897+
* will give an ambiguity quickly.
1049898
*/
1050899
def prefer(cand1: Candidate, cand2: Candidate): Boolean = {
1051900
val level1 = cand1.level
@@ -1064,9 +913,7 @@ trait Implicits { self: Typer =>
1064913
false
1065914
}
1066915

1067-
/** Sort list of implicit references according to their popularity
1068-
* (# of times each was picked in current run).
1069-
*/
916+
/** Sort list of implicit references according to `prefer` */
1070917
def sort(eligible: List[Candidate]) = eligible match {
1071918
case Nil => eligible
1072919
case e1 :: Nil => eligible
@@ -1077,23 +924,19 @@ trait Implicits { self: Typer =>
1077924
eligible.sortWith(prefer)
1078925
}
1079926

1080-
if (true) rank(sort(eligible), NoMatchingImplicitsFailure, Nil)
1081-
else if (true) go
1082-
else condense(rankImplicits(sort(eligible), Nil, Nil))
1083-
}
927+
rank(sort(eligible), NoMatchingImplicitsFailure, Nil)
928+
} // end searchImplicits
1084929

1085930
/** Find a unique best implicit reference */
1086931
def bestImplicit(contextual: Boolean): SearchResult = {
1087932
val eligible =
1088933
if (contextual) ctx.implicits.eligible(wildProto)
1089934
else implicitScope(wildProto).eligible
1090-
searchImplicits(eligible, contextual) match {
1091-
case success: SearchSuccess => success
1092-
case failure: SearchFailure =>
1093-
failure.reason match {
1094-
case (_: AmbiguousImplicits) | (_: DivergingImplicit) => failure
1095-
case _ => if (contextual) bestImplicit(contextual = false) else failure
1096-
}
935+
searchImplicits(eligible, contextual).recoverWith {
936+
failure => failure.reason match {
937+
case (_: AmbiguousImplicits) | (_: DivergingImplicit) => failure
938+
case _ => if (contextual) bestImplicit(contextual = false) else failure
939+
}
1097940
}
1098941
}
1099942

compiler/src/dotty/tools/dotc/util/PriorityGraph.scala

Lines changed: 0 additions & 96 deletions
This file was deleted.

0 commit comments

Comments
 (0)