Skip to content

Commit 2266d1d

Browse files
committed
Refactor divergence checking
Move core operations from SearchHistory to ImplicitSearch. This gives more opportunities for sharing and optimizations.
1 parent 48415d2 commit 2266d1d

File tree

2 files changed

+118
-115
lines changed

2 files changed

+118
-115
lines changed

compiler/src/dotty/tools/dotc/transform/TypeUtils.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ object TypeUtils {
2121
def isPrimitiveValueType(using Context): Boolean =
2222
self.classSymbol.isPrimitiveValueClass
2323

24+
def isByName: Boolean =
25+
self.isInstanceOf[ExprType]
26+
2427
def ensureMethodic(using Context): Type = self match {
2528
case self: MethodicType => self
2629
case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self)

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

Lines changed: 115 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import scala.annotation.internal.sharable
4040
import scala.annotation.threadUnsafe
4141

4242
/** Implicit resolution */
43-
object Implicits {
43+
object Implicits:
4444
import tpd._
4545

4646
/** An implicit definition `implicitRef` that is visible under a different name, `alias`.
@@ -499,7 +499,7 @@ object Implicits {
499499
class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType:
500500
def argument = EmptyTree
501501
def explanation(using Context) = em"$extApp does not $qualify"
502-
}
502+
end Implicits
503503

504504
import Implicits._
505505

@@ -723,7 +723,8 @@ trait ImplicitRunInfo:
723723
end ImplicitRunInfo
724724

725725
/** The implicit resolution part of type checking */
726-
trait Implicits { self: Typer =>
726+
trait Implicits:
727+
self: Typer =>
727728

728729
import tpd._
729730

@@ -1082,40 +1083,38 @@ trait Implicits { self: Typer =>
10821083
}
10831084

10841085
/** An implicit search; parameters as in `inferImplicit` */
1085-
class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context) {
1086+
class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context):
10861087
assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType],
10871088
em"found: $argument: ${argument.tpe}, expected: $pt")
10881089

10891090
private def nestedContext() =
10901091
ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled)
10911092

1092-
private def implicitProto(resultType: Type, f: Type => Type) =
1093-
if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType))
1094-
// Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though.
1095-
10961093
private def isCoherent = pt.isRef(defn.EqlClass)
10971094

1098-
/** The expected type for the searched implicit */
1099-
@threadUnsafe lazy val fullProto: Type = implicitProto(pt, identity)
1095+
val wideProto = pt.widenExpr
11001096

11011097
/** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */
1102-
val wildProto: Type = implicitProto(pt, wildApprox(_))
1098+
val wildProto: Type =
1099+
if argument.isEmpty then wildApprox(pt)
1100+
else ViewProto(wildApprox(argument.tpe.widen), wildApprox(pt))
1101+
// Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though.
11031102

11041103
val isNot: Boolean = wildProto.classSymbol == defn.NotClass
11051104

11061105
/** Try to type-check implicit reference, after checking that this is not
11071106
* a diverging search
11081107
*/
11091108
def tryImplicit(cand: Candidate, contextual: Boolean): SearchResult =
1110-
if (ctx.searchHistory.checkDivergence(cand, pt))
1111-
SearchFailure(new DivergingImplicit(cand.ref, pt.widenExpr, argument))
1109+
if checkDivergence(cand) then
1110+
SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument))
11121111
else {
11131112
val history = ctx.searchHistory.nest(cand, pt)
11141113
val result =
11151114
typedImplicit(cand, pt, argument, span)(using nestedContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history))
11161115
result match {
11171116
case res: SearchSuccess =>
1118-
ctx.searchHistory.defineBynameImplicit(pt.widenExpr, res)
1117+
ctx.searchHistory.defineBynameImplicit(wideProto, res)
11191118
case _ =>
11201119
result
11211120
}
@@ -1306,7 +1305,7 @@ trait Implicits { self: Typer =>
13061305
// effectively in a more inner context than any other definition provided by
13071306
// explicit definitions. Consequently these terms have the highest priority and no
13081307
// other candidates need to be considered.
1309-
ctx.searchHistory.recursiveRef(pt) match {
1308+
recursiveRef(pt) match {
13101309
case ref: TermRef =>
13111310
SearchSuccess(tpd.ref(ref).withSpan(span.startPos), ref, 0)(ctx.typerState, ctx.gadt)
13121311
case _ =>
@@ -1346,8 +1345,101 @@ trait Implicits { self: Typer =>
13461345
case success: SearchSuccess => success.ref
13471346
}
13481347
}
1349-
}
1350-
}
1348+
1349+
/** Fields needed for divergence checking */
1350+
@threadUnsafe lazy val ptCoveringSet = wideProto.coveringSet
1351+
@threadUnsafe lazy val ptSize = wideProto.typeSize
1352+
@threadUnsafe lazy val wildPt = wildApprox(wideProto)
1353+
1354+
/**
1355+
* Check if the supplied candidate implicit and target type indicate a diverging
1356+
* implicit search.
1357+
*
1358+
* @param cand The candidate implicit to be explored.
1359+
* @param pt The target type for the above candidate.
1360+
* @result True if this candidate/pt are divergent, false otherwise.
1361+
*/
1362+
def checkDivergence(cand: Candidate)(using Context): Boolean =
1363+
// For full details of the algorithm see the SIP:
1364+
// https://docs.scala-lang.org/sips/byname-implicits.html
1365+
util.Stats.record("checkDivergence")
1366+
1367+
// Unless we are able to tie a recursive knot, we report divergence if there is an
1368+
// open implicit using the same candidate implicit definition which has a type which
1369+
// is larger (see `typeSize`) and is constructed using the same set of types and type
1370+
// constructors (see `coveringSet`).
1371+
//
1372+
// We are able to tie a recursive knot if there is compatible term already under
1373+
// construction which is separated from this context by at least one by name argument
1374+
// as we ascend the chain of open implicits to the outermost search context.
1375+
1376+
@tailrec
1377+
def loop(history: SearchHistory, belowByname: Boolean): Boolean =
1378+
history match
1379+
case OpenSearch(cand1, tp, outer) =>
1380+
if cand1.ref eq cand.ref then
1381+
util.Stats.record("checkDivergence for sure")
1382+
val wideTp = tp.widenExpr
1383+
lazy val wildTp = wildApprox(wideTp)
1384+
lazy val tpSize = wideTp.typeSize
1385+
if belowByname && (wildTp <:< wildPt) then
1386+
false
1387+
else if tpSize > ptSize || wideTp.coveringSet != ptCoveringSet then
1388+
loop(outer, tp.isByName || belowByname)
1389+
else
1390+
tpSize < ptSize
1391+
|| wildTp =:= wildPt
1392+
|| loop(outer, tp.isByName || belowByname)
1393+
else loop(outer, tp.isByName || belowByname)
1394+
case _ => false
1395+
1396+
loop(ctx.searchHistory, pt.isByName)
1397+
end checkDivergence
1398+
1399+
/**
1400+
* Return the reference, if any, to a term under construction or already constructed in
1401+
* the current search history corresponding to the supplied target type.
1402+
*
1403+
* A term is eligible if its type is a subtype of the target type and either it has
1404+
* already been constructed and is present in the current implicit dictionary, or it is
1405+
* currently under construction and is separated from the current search context by at
1406+
* least one by name argument position.
1407+
*
1408+
* Note that because any suitable term found is defined as part of this search it will
1409+
* always be effectively in a more inner context than any other definition provided by
1410+
* explicit definitions. Consequently these terms have the highest priority and no other
1411+
* candidates need to be considered.
1412+
*
1413+
* @param pt The target type being searched for.
1414+
* @result The corresponding dictionary reference if any, NoType otherwise.
1415+
*/
1416+
def recursiveRef(pt: Type)(using Context): Type =
1417+
val widePt = pt.widenExpr
1418+
1419+
ctx.searchHistory.refBynameImplicit(widePt).orElse {
1420+
val bynamePt = pt.isByName
1421+
if (!ctx.searchHistory.byname && !bynamePt) NoType // No recursion unless at least one open implicit is by name ...
1422+
else {
1423+
// We are able to tie a recursive knot if there is compatible term already under
1424+
// construction which is separated from this context by at least one by name
1425+
// argument as we ascend the chain of open implicits to the outermost search
1426+
// context.
1427+
@tailrec
1428+
def loop(history: SearchHistory, belowByname: Boolean): Type =
1429+
history match
1430+
case OpenSearch(cand, tp, outer) =>
1431+
if (belowByname || tp.isByName) && tp.widenExpr <:< widePt then tp
1432+
else loop(outer, belowByname || tp.isByName)
1433+
case _ => NoType
1434+
1435+
loop(ctx.searchHistory, bynamePt) match
1436+
case NoType => NoType
1437+
case tp => ctx.searchHistory.linkBynameImplicit(tp.widenExpr)
1438+
}
1439+
}
1440+
end recursiveRef
1441+
end ImplicitSearch
1442+
end Implicits
13511443

13521444
/**
13531445
* Records the history of currently open implicit searches.
@@ -1378,97 +1470,6 @@ abstract class SearchHistory:
13781470

13791471
def isByname(tp: Type): Boolean = tp.isInstanceOf[ExprType]
13801472

1381-
/**
1382-
* Check if the supplied candidate implicit and target type indicate a diverging
1383-
* implicit search.
1384-
*
1385-
* @param cand The candidate implicit to be explored.
1386-
* @param pt The target type for the above candidate.
1387-
* @result True if this candidate/pt are divergent, false otherwise.
1388-
*/
1389-
def checkDivergence(cand: Candidate, pt: Type)(using Context): Boolean = {
1390-
// For full details of the algorithm see the SIP:
1391-
// https://docs.scala-lang.org/sips/byname-implicits.html
1392-
1393-
val widePt = pt.widenExpr
1394-
lazy val ptCoveringSet = widePt.coveringSet
1395-
lazy val ptSize = widePt.typeSize
1396-
lazy val wildPt = wildApprox(widePt)
1397-
1398-
// Unless we are able to tie a recursive knot, we report divergence if there is an
1399-
// open implicit using the same candidate implicit definition which has a type which
1400-
// is larger (see `typeSize`) and is constructed using the same set of types and type
1401-
// constructors (see `coveringSet`).
1402-
//
1403-
// We are able to tie a recursive knot if there is compatible term already under
1404-
// construction which is separated from this context by at least one by name argument
1405-
// as we ascend the chain of open implicits to the outermost search context.
1406-
1407-
@tailrec
1408-
def loop(history: SearchHistory, belowByname: Boolean): Boolean =
1409-
history match
1410-
case _: SearchRoot => false
1411-
case OpenSearch(cand1, tp, outer) =>
1412-
if cand1.ref == cand.ref then
1413-
val wideTp = tp.widenExpr
1414-
lazy val wildTp = wildApprox(wideTp)
1415-
lazy val tpSize = wideTp.typeSize
1416-
if belowByname && (wildTp <:< wildPt) then
1417-
false
1418-
else if tpSize > ptSize || wideTp.coveringSet != ptCoveringSet then
1419-
loop(outer, isByname(tp) || belowByname)
1420-
else
1421-
tpSize < ptSize
1422-
|| wildTp =:= wildPt
1423-
|| loop(outer, isByname(tp) || belowByname)
1424-
else loop(outer, isByname(tp) || belowByname)
1425-
1426-
loop(this, isByname(pt))
1427-
}
1428-
1429-
/**
1430-
* Return the reference, if any, to a term under construction or already constructed in
1431-
* the current search history corresponding to the supplied target type.
1432-
*
1433-
* A term is eligible if its type is a subtype of the target type and either it has
1434-
* already been constructed and is present in the current implicit dictionary, or it is
1435-
* currently under construction and is separated from the current search context by at
1436-
* least one by name argument position.
1437-
*
1438-
* Note that because any suitable term found is defined as part of this search it will
1439-
* always be effectively in a more inner context than any other definition provided by
1440-
* explicit definitions. Consequently these terms have the highest priority and no other
1441-
* candidates need to be considered.
1442-
*
1443-
* @param pt The target type being searched for.
1444-
* @result The corresponding dictionary reference if any, NoType otherwise.
1445-
*/
1446-
def recursiveRef(pt: Type)(using Context): Type = {
1447-
val widePt = pt.widenExpr
1448-
1449-
refBynameImplicit(widePt).orElse {
1450-
val bynamePt = isByname(pt)
1451-
if (!byname && !bynamePt) NoType // No recursion unless at least one open implicit is by name ...
1452-
else {
1453-
// We are able to tie a recursive knot if there is compatible term already under
1454-
// construction which is separated from this context by at least one by name
1455-
// argument as we ascend the chain of open implicits to the outermost search
1456-
// context.
1457-
@tailrec
1458-
def loop(history: SearchHistory, belowByname: Boolean): Type =
1459-
history match
1460-
case OpenSearch(cand, tp, outer) =>
1461-
if (belowByname || isByname(tp)) && tp.widenExpr <:< widePt then tp
1462-
else loop(outer, belowByname || isByname(tp))
1463-
case _ => NoType
1464-
1465-
loop(this, bynamePt) match
1466-
case NoType => NoType
1467-
case tp => ctx.searchHistory.linkBynameImplicit(tp.widenExpr)
1468-
}
1469-
}
1470-
}
1471-
14721473
// The following are delegated to the root of this search history.
14731474
def linkBynameImplicit(tpe: Type)(using Context): TermRef =
14741475
root.linkBynameImplicit(tpe)
@@ -1498,12 +1499,11 @@ final class SearchRoot extends SearchHistory:
14981499
def open = Nil
14991500

15001501
/** The dictionary of recursive implicit types and corresponding terms for this search. */
1501-
var implicitDictionary0: mutable.Map[Type, (TermRef, tpd.Tree)] = null
1502-
def implicitDictionary = {
1503-
if (implicitDictionary0 == null)
1504-
implicitDictionary0 = mutable.Map.empty[Type, (TermRef, tpd.Tree)]
1505-
implicitDictionary0
1506-
}
1502+
var myImplicitDictionary: mutable.Map[Type, (TermRef, tpd.Tree)] = null
1503+
private def implicitDictionary =
1504+
if myImplicitDictionary == null then
1505+
myImplicitDictionary = mutable.Map.empty[Type, (TermRef, tpd.Tree)]
1506+
myImplicitDictionary
15071507

15081508
/**
15091509
* Link a reference to an under-construction implicit for the provided type to its
@@ -1593,7 +1593,7 @@ final class SearchRoot extends SearchHistory:
15931593
}
15941594

15951595
val pruned = prune(List(tree), implicitDictionary.map(_._2).toList, Nil)
1596-
implicitDictionary0 = null
1596+
myImplicitDictionary = null
15971597
if (pruned.isEmpty) result
15981598
else if (pruned.exists(_._2 == EmptyTree)) NoMatchingImplicitsFailure
15991599
else {

0 commit comments

Comments
 (0)