Skip to content

Commit fb100cc

Browse files
committed
Add documentation
1 parent 8c1c71c commit fb100cc

File tree

1 file changed

+142
-2
lines changed

1 file changed

+142
-2
lines changed

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

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
11541174
abstract 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+
*/
12271301
final 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

Comments
 (0)