Skip to content

Commit 645b0f3

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

File tree

5 files changed

+207
-17
lines changed

5 files changed

+207
-17
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/ast/untpd.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
508508
/** A repeated argument such as `arg: _*` */
509509
def repeated(arg: Tree)(implicit ctx: Context): Typed = Typed(arg, Ident(tpnme.WILDCARD_STAR))
510510

511-
// ----- Accessing modifiers ----------------------------------------------------
512511

513512
// --------- Copier/Transformer/Accumulator classes for untyped trees -----
514513

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: 200 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,11 @@ object Implicits {
474474
import Implicits._
475475

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

646822
/** The implicit resolution part of type checking */
647823
trait Implicits { self: Typer =>
@@ -908,6 +1084,7 @@ trait Implicits { self: Typer =>
9081084
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}")
9091085
result
9101086
case result: SearchFailure if result.isAmbiguous =>
1087+
//println(i"FAIL for $pt / $argument: $result0")
9111088
val deepPt = pt.deepenProto
9121089
if (deepPt ne pt) inferImplicit(deepPt, argument, span)
9131090
else if (migrateTo3 && !ctx.mode.is(Mode.OldOverloadingResolution))
@@ -922,8 +1099,10 @@ trait Implicits { self: Typer =>
9221099
}
9231100
else result
9241101
case NoMatchingImplicitsFailure =>
1102+
//println(i"FAIL for $pt / $argument: $result0")
9251103
SearchFailure(new NoMatchingImplicits(pt, argument, ctx.typerState.constraint))
9261104
case _ =>
1105+
//println(i"FAIL for $pt / $argument: $result0")
9271106
result0
9281107
}
9291108
// If we are at the outermost implicit search then emit the implicit dictionary, if any.
@@ -1247,7 +1426,17 @@ trait Implicits { self: Typer =>
12471426
}
12481427
}
12491428

1250-
def implicitScope(tp: Type): OfTypeImplicits = ctx.run.implicitScope(tp, ctx)
1429+
def implicitScope(tp: Type): OfTypeImplicits =
1430+
val now = ctx.run.implicitScope2(tp)
1431+
if false then
1432+
val old = ctx.run.implicitScope1(tp, ctx)
1433+
val olds = old.companionRefs.toList
1434+
val nows = now.companionRefs.toList
1435+
if olds != nows then println(
1436+
i"""different implicit scopes for $tp
1437+
|old: $olds%, %
1438+
|new: $nows%, %""")
1439+
now
12511440

12521441
/** All available implicits, without ranking */
12531442
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)