@@ -851,6 +851,7 @@ trait Implicits { self: Typer =>
851851 case _ =>
852852 result0
853853 }
854+ // If we are at the outermost implicit search then emit the implicit dictionary, if any.
854855 ctx.searchHistory.emitDictionary(pos, result)
855856 }
856857 }
@@ -1107,6 +1108,14 @@ trait Implicits { self: Typer =>
11071108
11081109 /** Find a unique best implicit reference */
11091110 def bestImplicit (contextual : Boolean ): SearchResult = {
1111+ // Before searching for contextual or implicit scope candidates we first check if
1112+ // there is an under construction or already constructed term with which we can tie
1113+ // the knot.
1114+ //
1115+ // Since any suitable term found is defined as part of this search it will always be
1116+ // effectively in a more inner context than any other definition provided by
1117+ // explicit definitions. Consequently these terms have the highest priority and no
1118+ // other candidates need to be considered.
11101119 ctx.searchHistory.recursiveRef(pt) match {
11111120 case ref : TermRef =>
11121121 SearchSuccess (tpd.ref(ref).withPos(pos.startPos), ref, 0 )(ctx.typerState)
@@ -1149,13 +1158,31 @@ trait Implicits { self: Typer =>
11491158 }
11501159}
11511160
1152- /** Records the history of currently open implicit searches
1161+ /**
1162+ * Records the history of currently open implicit searches.
1163+ *
1164+ * A search history maintains a list of open implicit searches (`open`) a shortcut flag
1165+ * indicating whether any of these are by name (`byname`) and a reference to the root
1166+ * search history (`root`) which in turn maintains a possibly empty dictionary of
1167+ * recursive implicit terms constructed during this search.
1168+ *
1169+ * A search history provides operations to created a nested search history, check for
1170+ * divergence, enter by name references and definitions in the implicit dictionary, lookup
1171+ * recursive references and emit a complete implicit dictionary when the outermost search
1172+ * is complete.
11531173 */
11541174abstract class SearchHistory { outer =>
11551175 val root : SearchRoot
11561176 val open : List [(Candidate , Type )]
1177+ /** Does this search history contain any by name implicit arguments. */
11571178 val byname : Boolean
11581179
1180+ /**
1181+ * Create the state for a nested implicit search.
1182+ * @param cand The candidate implicit to be explored.
1183+ * @param pt The target type for the above candidate.
1184+ * @result The nested history.
1185+ */
11591186 def nest (cand : Candidate , pt : Type )(implicit ctx : Context ): SearchHistory = {
11601187 new SearchHistory {
11611188 val root = outer.root
@@ -1166,12 +1193,32 @@ abstract class SearchHistory { outer =>
11661193
11671194 def isByname (tp : Type ): Boolean = tp.isInstanceOf [ExprType ]
11681195
1196+ /**
1197+ * Check if the supplied candidate implicit and target type indicate a diverging
1198+ * implicit search.
1199+ *
1200+ * @param cand The candidate implicit to be explored.
1201+ * @param pt The target type for the above candidate.
1202+ * @result True if this candidate/pt are divergent, false otherwise.
1203+ */
11691204 def checkDivergence (cand : Candidate , pt : Type )(implicit ctx : Context ): Boolean = {
1205+ // For full details of the algorithm see the SIP:
1206+ // https://docs.scala-lang.org/sips/byname-implicits.html
1207+
11701208 val widePt = pt.widenExpr
11711209 lazy val ptCoveringSet = widePt.coveringSet
11721210 lazy val ptSize = widePt.typeSize
11731211 lazy val wildPt = wildApprox(widePt)
11741212
1213+ // Unless we are able to tie a recursive knot, we report divergence if there is an
1214+ // open implicit using the same candidate implicit definition which has a type which
1215+ // is larger (see `typeSize`) and is constructed using the same set of types and type
1216+ // constructors (see `coveringSet`).
1217+ //
1218+ // We are able to tie a recursive knot if there is compatible term already under
1219+ // construction which is separated from this context by at least one by name argument
1220+ // as we ascend the chain of open implicits to the outermost search context.
1221+
11751222 @ tailrec
11761223 def loop (ois : List [(Candidate , Type )], belowByname : Boolean ): Boolean = {
11771224 ois match {
@@ -1192,13 +1239,34 @@ abstract class SearchHistory { outer =>
11921239 loop(open, isByname(pt))
11931240 }
11941241
1242+ /**
1243+ * Return the reference, if any, to a term under construction or already constructed in
1244+ * the current search history corresponding to the supplied target type.
1245+ *
1246+ * A term is eligible if its type is a subtype of the target type and either it has
1247+ * already been constructed and is present in the current implicit dictionary, or it is
1248+ * currently under construction and is separated from the current search context by at
1249+ * least one by name argument position.
1250+ *
1251+ * Note that because any suitable term found is defined as part of this search it will
1252+ * always be effectively in a more inner context than any other definition provided by
1253+ * explicit definitions. Consequently these terms have the highest priority and no other
1254+ * candidates need to be considered.
1255+ *
1256+ * @param pt The target type being searched for.
1257+ * @result The corresponding dictionary reference if any, NoType otherwise.
1258+ */
11951259 def recursiveRef (pt : Type )(implicit ctx : Context ): Type = {
11961260 val widePt = pt.widenExpr
11971261
11981262 refBynameImplicit(widePt).orElse {
11991263 val bynamePt = isByname(pt)
1200- if (! byname && ! bynamePt) NoType
1264+ if (! byname && ! bynamePt) NoType // No recursion unless at least one open implicit is by name ...
12011265 else {
1266+ // We are able to tie a recursive knot if there is compatible term already under
1267+ // construction which is separated from this context by at least one by name
1268+ // argument as we ascend the chain of open implicits to the outermost search
1269+ // context.
12021270 @ tailrec
12031271 def loop (ois : List [(Candidate , Type )], belowByname : Boolean ): Type = {
12041272 ois match {
@@ -1216,26 +1284,41 @@ abstract class SearchHistory { outer =>
12161284 }
12171285 }
12181286
1287+ // The following are delegated to the root of this search history.
12191288 def linkBynameImplicit (tpe : Type )(implicit ctx : Context ): TermRef = root.linkBynameImplicit(tpe)
12201289 def refBynameImplicit (tpe : Type )(implicit ctx : Context ): Type = root.refBynameImplicit(tpe)
12211290 def defineBynameImplicit (tpe : Type , result : SearchSuccess )(implicit ctx : Context ): SearchResult = root.defineBynameImplicit(tpe, result)
1291+
1292+ // This is NOOP unless at the root of this search history.
12221293 def emitDictionary (pos : Position , result : SearchResult )(implicit ctx : Context ): SearchResult = result
12231294
12241295 override def toString : String = s " SearchHistory(open = $open, byname = $byname) "
12251296}
12261297
1298+ /**
1299+ * The the state corresponding to the outermost context of an implicit searcch.
1300+ */
12271301final class SearchRoot extends SearchHistory {
12281302 val root = this
12291303 val open = Nil
12301304 val byname = false
12311305
1306+ /** The dictionary of recursive implicit types and corresponding terms for this search. */
12321307 var implicitDictionary0 : mutable.Map [Type , (TermRef , tpd.Tree )] = null
12331308 def implicitDictionary = {
12341309 if (implicitDictionary0 == null )
12351310 implicitDictionary0 = mutable.Map .empty[Type , (TermRef , tpd.Tree )]
12361311 implicitDictionary0
12371312 }
12381313
1314+ /**
1315+ * Link a reference to an under-construction implicit for the provided type to its
1316+ * defining occurrence via the implicit dictionary, creating a dictionary entry for this
1317+ * type if one does not yet exist.
1318+ *
1319+ * @param tpe The type to link.
1320+ * @result The TermRef of the corresponding dictionary entry.
1321+ */
12391322 override def linkBynameImplicit (tpe : Type )(implicit ctx : Context ): TermRef = {
12401323 implicitDictionary.get(tpe) match {
12411324 case Some ((ref, _)) => ref
@@ -1247,10 +1330,32 @@ final class SearchRoot extends SearchHistory {
12471330 }
12481331 }
12491332
1333+ /**
1334+ * Look up an implicit dictionary entry by type.
1335+ *
1336+ * If present yield the TermRef corresponding to the eventual dictionary entry,
1337+ * otherwise NoType.
1338+ *
1339+ * @param tpe The type to look up.
1340+ * @result The corresponding TermRef, or NoType if none.
1341+ */
12501342 override def refBynameImplicit (tpe : Type )(implicit ctx : Context ): Type = {
12511343 implicitDictionary.get(tpe).map(_._1).getOrElse(NoType )
12521344 }
12531345
1346+ /**
1347+ * Define a pending dictionary entry if any.
1348+ *
1349+ * If the provided type corresponds to an under-construction by name implicit, then use
1350+ * the tree contained in the provided SearchSuccess as its definition, returning an
1351+ * updated result referring to dictionary entry. Otherwise return the SearchSuccess
1352+ * unchanged.
1353+ *
1354+ * @param tpe The type for which the entry is to be defined
1355+ * @param result The SearchSuccess corresponding to tpe
1356+ * @result A SearchResult referring to the newly created dictionary entry if tpe
1357+ * is an under-construction by name implicit, the provided result otherwise.
1358+ */
12541359 override def defineBynameImplicit (tpe : Type , result : SearchSuccess )(implicit ctx : Context ): SearchResult = {
12551360 implicitDictionary.get(tpe) match {
12561361 case Some ((ref, _)) =>
@@ -1260,6 +1365,14 @@ final class SearchRoot extends SearchHistory {
12601365 }
12611366 }
12621367
1368+ /**
1369+ * Emit the implicit dictionary at the completion of an implicit search.
1370+ *
1371+ * @param pos The position at which the search is elaborated.
1372+ * @param result The result of the search prior to substitution of recursive references.
1373+ * @result The elaborated result, comprising the implicit dictionary and a result tree
1374+ * substituted with references into the dictionary.
1375+ */
12631376 override def emitDictionary (pos : Position , result : SearchResult )(implicit ctx : Context ): SearchResult = {
12641377 if (implicitDictionary == null || implicitDictionary.isEmpty) result
12651378 else {
@@ -1268,6 +1381,11 @@ final class SearchRoot extends SearchHistory {
12681381 case success@ SearchSuccess (tree, _, _) =>
12691382 import tpd ._
12701383
1384+ // We might have accumulated dictionary entries for by name implicit arguments
1385+ // which are not in fact used recursively either directly in the outermost result
1386+ // term, or indirectly via other dictionary entries. We prune these out, recursively
1387+ // eliminating entries until all remaining entries are at least transtively referred
1388+ // to in the outermost result term.
12711389 @ tailrec
12721390 def prune (trees : List [Tree ], pending : List [(TermRef , Tree )], acc : List [(TermRef , Tree )]): List [(TermRef , Tree )] = pending match {
12731391 case Nil => acc
@@ -1287,13 +1405,34 @@ final class SearchRoot extends SearchHistory {
12871405 implicitDictionary0 = null
12881406 if (pruned.isEmpty) result
12891407 else {
1408+ // If there are any dictionary entries remaining after pruning, construct a dictionary
1409+ // class of the form,
1410+ //
1411+ // class <dictionary> {
1412+ // val $_lazy_implicit_$0 = ...
1413+ // ...
1414+ // val $_lazy_implicit_$n = ...
1415+ // }
1416+ //
1417+ // Where the RHSs of the $_lazy_implicit_$n are the terms used to populate the dictionary
1418+ // via defineByNameImplicit.
1419+ //
1420+ // The returned search result is then of the form,
1421+ //
1422+ // {
1423+ // class <dictionary> { ... }
1424+ // val $_lazy_implicit_$nn = new <dictionary>
1425+ // result.tree // with dictionary references substituted in
1426+ // }
1427+
12901428 val parents = List (defn.ObjectType , defn.SerializableType )
12911429 val classSym = ctx.newNormalizedClassSymbol(ctx.owner, LazyImplicitName .fresh().toTypeName, Synthetic | Final , parents, coord = pos)
12921430 val vsyms = pruned.map(_._1.symbol)
12931431 val nsyms = vsyms.map(vsym => ctx.newSymbol(classSym, vsym.name, EmptyFlags , vsym.info, coord = pos).entered)
12941432 val vsymMap = (vsyms zip nsyms).toMap
12951433
12961434 val rhss = pruned.map(_._2)
1435+ // Substitute dictionary references into dictionary entry RHSs
12971436 val rhsMap = new TreeTypeMap (treeMap = {
12981437 case id : Ident if vsymMap.contains(id.symbol) =>
12991438 tpd.ref(vsymMap(id.symbol))
@@ -1311,6 +1450,7 @@ final class SearchRoot extends SearchHistory {
13111450 val valSym = ctx.newLazyImplicit(classSym.typeRef, pos)
13121451 val inst = ValDef (valSym, New (classSym.typeRef, Nil ))
13131452
1453+ // Substitute dictionary references into outermost result term.
13141454 val resMap = new TreeTypeMap (treeMap = {
13151455 case id : Ident if vsymMap.contains(id.symbol) =>
13161456 Select (tpd.ref(valSym), id.name)
0 commit comments