Skip to content

Commit 2446922

Browse files
committed
Try new implicitScope scheme
1 parent 88b475b commit 2446922

File tree

4 files changed

+182
-14
lines changed

4 files changed

+182
-14
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,7 @@ object Trees {
15401540
receiver: tpd.Tree, method: TermName, args: List[Tree], targs: List[Type],
15411541
expectedType: Type)(using parentCtx: Context): tpd.Tree = {
15421542
given ctx as Context = parentCtx.retractMode(Mode.ImplicitsEnabled)
1543+
import dotty.tools.dotc.ast.tpd.TreeOps
15431544

15441545
val typer = ctx.typer
15451546
val proto = FunProto(args, expectedType)

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5451,6 +5451,10 @@ object Types {
54515451
def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp)
54525452
}
54535453

5454+
class TypeHashSet extends util.HashSet[Type](64):
5455+
override def hash(x: Type): Int = System.identityHashCode(x)
5456+
override def isEqual(x: Type, y: Type) = x.eq(y)
5457+
54545458
class NamedPartsAccumulator(p: NamedType => Boolean,
54555459
widenSingletons: Boolean = false, // if set, also consider underlying types in singleton path prefixes
54565460
excludeLowerBounds: Boolean = false)
@@ -5461,10 +5465,7 @@ object Types {
54615465
def maybeAdd(x: mutable.Set[NamedType], tp: NamedType): mutable.Set[NamedType] =
54625466
if (p(tp)) x += tp else x
54635467

5464-
val seen: util.HashSet[Type] = new util.HashSet[Type](64) {
5465-
override def hash(x: Type): Int = System.identityHashCode(x)
5466-
override def isEqual(x: Type, y: Type) = x.eq(y)
5467-
}
5468+
val seen = TypeHashSet()
54685469

54695470
override def applyToPrefix(x: mutable.Set[NamedType], tp: NamedType): mutable.Set[NamedType] =
54705471
tp.prefix match

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

Lines changed: 175 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,8 @@ import Implicits._
477477
trait ImplicitRunInfo {
478478
self: Run =>
479479

480-
private val implicitScopeCache = mutable.AnyRefMap[Type, OfTypeImplicits]()
480+
private val implicitScopeCache1 = mutable.AnyRefMap[Type, OfTypeImplicits]()
481+
private val implicitScopeCache2 = mutable.AnyRefMap[Type, OfTypeImplicits]()
481482

482483
private val EmptyTermRefSet = new TermRefSet(using NoContext)
483484

@@ -504,8 +505,7 @@ trait ImplicitRunInfo {
504505
* a type variable, we need the current context, the current
505506
* runinfo context does not do.
506507
*/
507-
def implicitScope(rootTp: Type, liftingCtx: Context): OfTypeImplicits = {
508-
508+
def implicitScope1(rootTp: Type, liftingCtx: Context): OfTypeImplicits = {
509509
val seen: mutable.Set[Type] = mutable.Set()
510510
val incomplete: mutable.Set[Type] = mutable.Set()
511511

@@ -557,7 +557,7 @@ trait ImplicitRunInfo {
557557
trace(i"collectCompanions($tp)", implicitsDetailed) {
558558
record("collectCompanions")
559559

560-
def iscopeRefs(t: Type): TermRefSet = implicitScopeCache.get(t) match {
560+
def iscopeRefs(t: Type): TermRefSet = implicitScopeCache1.get(t) match {
561561
case Some(is) =>
562562
is.companionRefs
563563
case None =>
@@ -568,7 +568,7 @@ trait ImplicitRunInfo {
568568
else {
569569
seen += t
570570
val is = iscope(t)
571-
if (!implicitScopeCache.contains(t)) incomplete += tp
571+
if (!implicitScopeCache1.contains(t)) incomplete += tp
572572
is.companionRefs
573573
}
574574
}
@@ -629,18 +629,171 @@ trait ImplicitRunInfo {
629629
if (canCache &&
630630
((tp eq rootTp) || // first type traversed is always cached
631631
!incomplete.contains(tp))) // other types are cached if they are not incomplete
632-
implicitScopeCache(tp) = result
632+
implicitScopeCache1(tp) = result
633633
result
634634
}
635-
if (canCache) implicitScopeCache.getOrElse(tp, computeIScope())
635+
if (canCache) implicitScopeCache1.getOrElse(tp, computeIScope())
636636
else computeIScope()
637637
}
638638

639639
iscope(rootTp)
640640
}
641641

642+
def implicitScope2(rootTp: Type)(using Context): OfTypeImplicits =
643+
644+
def isExcluded(sym: Symbol) =
645+
if migrateTo3 then false else sym.is(Package) || sym.isPackageObject
646+
647+
/** Is `sym` an anchor type for which givens may exist? Anchor types are classes,
648+
* opaque type aliases, and abstract types, but not type parameters or package objects.
649+
*/
650+
def isAnchor(sym: Symbol) =
651+
sym.isClass && !isExcluded(sym)
652+
|| sym.isOpaqueAlias
653+
|| sym.is(Deferred, butNot = Param)
654+
655+
object liftToAnchors extends TypeMap:
656+
private val partSeen = TypeHashSet()
657+
override def stopAtStatic = true
658+
def apply(t: Type) = t.dealias match
659+
case t: TypeRef => if isAnchor(t.symbol) then t else apply(t.superType)
660+
case t: TypeVar => apply(t.underlying)
661+
case t: ParamRef => apply(t.underlying)
662+
case t: ConstantType => apply(t.underlying)
663+
case t => mapOver(t)
664+
665+
object collectParts extends TypeTraverser:
666+
667+
private var provisional: Boolean = _
668+
private var parts: mutable.LinkedHashSet[Type] = _
669+
private val partSeen = TypeHashSet()
670+
671+
def traversePrefix(pre: Type): Unit = pre match
672+
case pre: TermRef =>
673+
if migrateTo3 then traverse(pre.info)
674+
traversePrefix(pre.prefix)
675+
case _ =>
676+
traverse(pre)
677+
678+
def traverse(t: Type) =
679+
if partSeen.contains(t) then ()
680+
else if implicitScopeCache2.contains(t) then parts += t
681+
else
682+
partSeen.addEntry(t)
683+
t.dealias match
684+
case t: TypeRef =>
685+
if isAnchor(t.symbol) then
686+
parts += t
687+
traversePrefix(t.prefix)
688+
else
689+
traverse(t.underlying)
690+
case t: TermRef =>
691+
if !isExcluded(t.symbol) then
692+
traverse(t.info)
693+
traversePrefix(t.prefix)
694+
case t: ThisType if t.cls.is(Module) && t.cls.isStaticOwner =>
695+
traverse(t.cls.sourceModule.termRef)
696+
case t: ConstantType =>
697+
traverse(t.underlying)
698+
case t: TypeParamRef =>
699+
traverse(t.underlying)
700+
if ctx.typerState.constraint.contains(t) then provisional = true
701+
case t: TermParamRef =>
702+
traverse(t.underlying)
703+
case t =>
704+
traverseChildren(t)
705+
706+
def apply(tp: Type): (collection.Set[Type], Boolean) =
707+
provisional = false
708+
parts = mutable.LinkedHashSet()
709+
partSeen.clear()
710+
traverse(tp)
711+
(parts, provisional)
712+
end collectParts
713+
714+
val seen = TypeHashSet()
715+
val incomplete = TypeHashSet()
716+
717+
def collectCompanions(tp: Type, parts: collection.Set[Type]): TermRefSet =
718+
719+
def iscopeRefs(t: Type): TermRefSet =
720+
implicitScopeCache2.get(t) match
721+
case Some(is) =>
722+
is.companionRefs
723+
case None =>
724+
if seen.contains(t) then
725+
incomplete.addEntry(tp) // all references for `t` will be accounted for in `seen` so we return `EmptySet`.
726+
EmptyTermRefSet // on the other hand, the refs of `tp` are now inaccurate, so `tp` is marked incomplete.
727+
else
728+
seen.addEntry(t)
729+
val is = computeIScope(t)
730+
if !implicitScopeCache2.contains(t) then incomplete.addEntry(tp)
731+
is.companionRefs
732+
end iscopeRefs
733+
734+
val companions = new TermRefSet
735+
736+
def addCompanion(pre: Type, companion: Symbol) =
737+
if companion.exists && !companion.isAbsent() then
738+
companions += TermRef(pre, companion)
739+
740+
def addCompanions(t: Type) = implicitScopeCache2.get(t) match
741+
case Some(iscope) =>
742+
companions ++= iscope.companionRefs
743+
case None => t match
744+
case t: TypeRef =>
745+
val sym = t.symbol
746+
val pre = t.prefix
747+
addPath(pre)
748+
addCompanion(pre,
749+
if sym.isClass then sym.companionModule
750+
else pre.member(sym.name.toTermName)
751+
.suchThat(companion => companion.is(Module) && companion.owner == sym.owner)
752+
.symbol)
753+
if sym.isClass then
754+
for p <- t.parents do companions ++= iscopeRefs(p)
755+
else
756+
companions ++= iscopeRefs(t.superType)
757+
end addCompanions
758+
759+
def addPath(pre: Type): Unit = pre.dealias match
760+
case pre: ThisType if pre.cls.is(Module) && pre.cls.isStaticOwner =>
761+
addPath(pre.cls.sourceModule.termRef)
762+
case pre: TermRef =>
763+
if pre.symbol.is(Package) then
764+
if migrateTo3 then
765+
addCompanion(pre, pre.member(nme.PACKAGE).symbol)
766+
addPath(pre.prefix)
767+
else if !pre.symbol.isPackageObject || migrateTo3 then
768+
companions += pre
769+
addPath(pre.prefix)
770+
case _ =>
771+
772+
parts.foreach(addCompanions)
773+
companions
774+
end collectCompanions
775+
776+
def computeIScope(tp: Type): OfTypeImplicits =
777+
val (parts, provisional) = collectParts(tp)
778+
val result = new OfTypeImplicits(tp, collectCompanions(tp, parts))(runContext)
779+
if Config.cacheImplicitScopes
780+
&& tp.hash != NotCached
781+
&& !provisional
782+
&& (tp eq rootTp) // first type traversed is always cached
783+
|| !incomplete.contains(tp) // other types are cached if they are not incomplete
784+
then implicitScopeCache2(tp) = result
785+
result
786+
787+
/** The implicit scope of type `tp` */
788+
def iscope(tp: Type): OfTypeImplicits =
789+
implicitScopeCache2.getOrElse(tp, computeIScope(tp))
790+
791+
iscope(rootTp)
792+
end implicitScope2
793+
642794
protected def reset(): Unit =
643-
implicitScopeCache.clear()
795+
implicitScopeCache1.clear()
796+
implicitScopeCache2.clear()
644797
}
645798

646799
/** The implicit resolution part of type checking */
@@ -908,6 +1061,7 @@ trait Implicits { self: Typer =>
9081061
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}")
9091062
result
9101063
case result: SearchFailure if result.isAmbiguous =>
1064+
//println(i"FAIL for $pt / $argument: $result0")
9111065
val deepPt = pt.deepenProto
9121066
if (deepPt ne pt) inferImplicit(deepPt, argument, span)
9131067
else if (migrateTo3 && !ctx.mode.is(Mode.OldOverloadingResolution))
@@ -922,8 +1076,10 @@ trait Implicits { self: Typer =>
9221076
}
9231077
else result
9241078
case NoMatchingImplicitsFailure =>
1079+
//println(i"FAIL for $pt / $argument: $result0")
9251080
SearchFailure(new NoMatchingImplicits(pt, argument, ctx.typerState.constraint))
9261081
case _ =>
1082+
//println(i"FAIL for $pt / $argument: $result0")
9271083
result0
9281084
}
9291085
// If we are at the outermost implicit search then emit the implicit dictionary, if any.
@@ -1247,7 +1403,17 @@ trait Implicits { self: Typer =>
12471403
}
12481404
}
12491405

1250-
def implicitScope(tp: Type): OfTypeImplicits = ctx.run.implicitScope(tp, ctx)
1406+
def implicitScope(tp: Type): OfTypeImplicits =
1407+
val now = ctx.run.implicitScope2(tp)
1408+
if false then
1409+
val old = ctx.run.implicitScope1(tp, ctx)
1410+
val olds = old.companionRefs.toList
1411+
val nows = now.companionRefs.toList
1412+
if olds != nows then println(
1413+
i"""different implicit scopes for $tp
1414+
|old: $olds%, %
1415+
|new: $nows%, %""")
1416+
now
12511417

12521418
/** All available implicits, without ranking */
12531419
def allImplicits: Set[TermRef] = {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ trait ImportSuggestions:
153153
val alreadyAvailableCandidates: Set[Symbol] = {
154154
val wildProto = wildApprox(pt)
155155
val contextualCandidates = ctx.implicits.eligible(wildProto)
156-
val implicitScopeCandidates = ctx.run.implicitScope(wildProto, ctx).eligible
156+
val implicitScopeCandidates = ctx.run.implicitScope2(wildProto).eligible
157157
val allCandidates = contextualCandidates ++ implicitScopeCandidates
158158
allCandidates.map(_.implicitRef.underlyingRef.symbol).toSet
159159
}

0 commit comments

Comments
 (0)