@@ -851,6 +851,7 @@ trait Implicits { self: Typer =>
851
851
case _ =>
852
852
result0
853
853
}
854
+ // If we are at the outermost implicit search then emit the implicit dictionary, if any.
854
855
ctx.searchHistory.emitDictionary(pos, result)
855
856
}
856
857
}
@@ -1107,6 +1108,14 @@ trait Implicits { self: Typer =>
1107
1108
1108
1109
/** Find a unique best implicit reference */
1109
1110
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.
1110
1119
ctx.searchHistory.recursiveRef(pt) match {
1111
1120
case ref : TermRef =>
1112
1121
SearchSuccess (tpd.ref(ref).withPos(pos.startPos), ref, 0 )(ctx.typerState)
@@ -1149,13 +1158,31 @@ trait Implicits { self: Typer =>
1149
1158
}
1150
1159
}
1151
1160
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.
1153
1173
*/
1154
1174
abstract class SearchHistory { outer =>
1155
1175
val root : SearchRoot
1156
1176
val open : List [(Candidate , Type )]
1177
+ /** Does this search history contain any by name implicit arguments. */
1157
1178
val byname : Boolean
1158
1179
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
+ */
1159
1186
def nest (cand : Candidate , pt : Type )(implicit ctx : Context ): SearchHistory = {
1160
1187
new SearchHistory {
1161
1188
val root = outer.root
@@ -1166,12 +1193,32 @@ abstract class SearchHistory { outer =>
1166
1193
1167
1194
def isByname (tp : Type ): Boolean = tp.isInstanceOf [ExprType ]
1168
1195
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
+ */
1169
1204
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
+
1170
1208
val widePt = pt.widenExpr
1171
1209
lazy val ptCoveringSet = widePt.coveringSet
1172
1210
lazy val ptSize = widePt.typeSize
1173
1211
lazy val wildPt = wildApprox(widePt)
1174
1212
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
+
1175
1222
@ tailrec
1176
1223
def loop (ois : List [(Candidate , Type )], belowByname : Boolean ): Boolean = {
1177
1224
ois match {
@@ -1192,13 +1239,34 @@ abstract class SearchHistory { outer =>
1192
1239
loop(open, isByname(pt))
1193
1240
}
1194
1241
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
+ */
1195
1259
def recursiveRef (pt : Type )(implicit ctx : Context ): Type = {
1196
1260
val widePt = pt.widenExpr
1197
1261
1198
1262
refBynameImplicit(widePt).orElse {
1199
1263
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 ...
1201
1265
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.
1202
1270
@ tailrec
1203
1271
def loop (ois : List [(Candidate , Type )], belowByname : Boolean ): Type = {
1204
1272
ois match {
@@ -1216,26 +1284,41 @@ abstract class SearchHistory { outer =>
1216
1284
}
1217
1285
}
1218
1286
1287
+ // The following are delegated to the root of this search history.
1219
1288
def linkBynameImplicit (tpe : Type )(implicit ctx : Context ): TermRef = root.linkBynameImplicit(tpe)
1220
1289
def refBynameImplicit (tpe : Type )(implicit ctx : Context ): Type = root.refBynameImplicit(tpe)
1221
1290
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.
1222
1293
def emitDictionary (pos : Position , result : SearchResult )(implicit ctx : Context ): SearchResult = result
1223
1294
1224
1295
override def toString : String = s " SearchHistory(open = $open, byname = $byname) "
1225
1296
}
1226
1297
1298
+ /**
1299
+ * The the state corresponding to the outermost context of an implicit searcch.
1300
+ */
1227
1301
final class SearchRoot extends SearchHistory {
1228
1302
val root = this
1229
1303
val open = Nil
1230
1304
val byname = false
1231
1305
1306
+ /** The dictionary of recursive implicit types and corresponding terms for this search. */
1232
1307
var implicitDictionary0 : mutable.Map [Type , (TermRef , tpd.Tree )] = null
1233
1308
def implicitDictionary = {
1234
1309
if (implicitDictionary0 == null )
1235
1310
implicitDictionary0 = mutable.Map .empty[Type , (TermRef , tpd.Tree )]
1236
1311
implicitDictionary0
1237
1312
}
1238
1313
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
+ */
1239
1322
override def linkBynameImplicit (tpe : Type )(implicit ctx : Context ): TermRef = {
1240
1323
implicitDictionary.get(tpe) match {
1241
1324
case Some ((ref, _)) => ref
@@ -1247,10 +1330,32 @@ final class SearchRoot extends SearchHistory {
1247
1330
}
1248
1331
}
1249
1332
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
+ */
1250
1342
override def refBynameImplicit (tpe : Type )(implicit ctx : Context ): Type = {
1251
1343
implicitDictionary.get(tpe).map(_._1).getOrElse(NoType )
1252
1344
}
1253
1345
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
+ */
1254
1359
override def defineBynameImplicit (tpe : Type , result : SearchSuccess )(implicit ctx : Context ): SearchResult = {
1255
1360
implicitDictionary.get(tpe) match {
1256
1361
case Some ((ref, _)) =>
@@ -1260,6 +1365,14 @@ final class SearchRoot extends SearchHistory {
1260
1365
}
1261
1366
}
1262
1367
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
+ */
1263
1376
override def emitDictionary (pos : Position , result : SearchResult )(implicit ctx : Context ): SearchResult = {
1264
1377
if (implicitDictionary == null || implicitDictionary.isEmpty) result
1265
1378
else {
@@ -1268,6 +1381,11 @@ final class SearchRoot extends SearchHistory {
1268
1381
case success@ SearchSuccess (tree, _, _) =>
1269
1382
import tpd ._
1270
1383
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.
1271
1389
@ tailrec
1272
1390
def prune (trees : List [Tree ], pending : List [(TermRef , Tree )], acc : List [(TermRef , Tree )]): List [(TermRef , Tree )] = pending match {
1273
1391
case Nil => acc
@@ -1287,13 +1405,34 @@ final class SearchRoot extends SearchHistory {
1287
1405
implicitDictionary0 = null
1288
1406
if (pruned.isEmpty) result
1289
1407
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
+
1290
1428
val parents = List (defn.ObjectType , defn.SerializableType )
1291
1429
val classSym = ctx.newNormalizedClassSymbol(ctx.owner, LazyImplicitName .fresh().toTypeName, Synthetic | Final , parents, coord = pos)
1292
1430
val vsyms = pruned.map(_._1.symbol)
1293
1431
val nsyms = vsyms.map(vsym => ctx.newSymbol(classSym, vsym.name, EmptyFlags , vsym.info, coord = pos).entered)
1294
1432
val vsymMap = (vsyms zip nsyms).toMap
1295
1433
1296
1434
val rhss = pruned.map(_._2)
1435
+ // Substitute dictionary references into dictionary entry RHSs
1297
1436
val rhsMap = new TreeTypeMap (treeMap = {
1298
1437
case id : Ident if vsymMap.contains(id.symbol) =>
1299
1438
tpd.ref(vsymMap(id.symbol))
@@ -1311,6 +1450,7 @@ final class SearchRoot extends SearchHistory {
1311
1450
val valSym = ctx.newLazyImplicit(classSym.typeRef, pos)
1312
1451
val inst = ValDef (valSym, New (classSym.typeRef, Nil ))
1313
1452
1453
+ // Substitute dictionary references into outermost result term.
1314
1454
val resMap = new TreeTypeMap (treeMap = {
1315
1455
case id : Ident if vsymMap.contains(id.symbol) =>
1316
1456
Select (tpd.ref(valSym), id.name)
0 commit comments