Skip to content

Cleanup handling of Contexts in RunInfo and Implicits #3679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class Run(comp: Compiler, ictx: Context) {
ctx.initialize()(start) // re-initialize the base context with start
def addImport(ctx: Context, refFn: () => TermRef) =
ctx.fresh.setImportInfo(ImportInfo.rootImport(refFn)(ctx))
(start.setRunInfo(new RunInfo(start)) /: defn.RootImportFns)(addImport)
(start.setRunInfo(new RunInfo) /: defn.RootImportFns)(addImport)
}

protected[this] implicit val ctx: Context = rootContext(ictx)
Expand Down Expand Up @@ -194,9 +194,7 @@ class Run(comp: Compiler, ictx: Context) {

object Run {
/** Info that changes on each compiler run */
class RunInfo(initctx: Context) extends ImplicitRunInfo with ConstraintRunInfo {
implicit val ctx: Context = initctx

class RunInfo extends ImplicitRunInfo with ConstraintRunInfo {
private[this] var myUnits: List[CompilationUnit] = _
private[this] var myUnitsCached: List[CompilationUnit] = _
private[this] var myFiles: Set[AbstractFile] = _
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ object Contexts {
else
outer.implicits
if (implicitRefs.isEmpty) outerImplicits
else new ContextualImplicits(implicitRefs, outerImplicits)(this)
else new ContextualImplicits(implicitRefs, outerImplicits, this)
}
implicitsCache
}
Expand Down Expand Up @@ -508,15 +508,15 @@ object Contexts {
moreProperties = Map.empty
store = initialStore
.updated(settingsStateLoc, settings.defaultState)
.updated(runInfoLoc, new RunInfo(this))
.updated(runInfoLoc, new RunInfo)
typeComparer = new TypeComparer(this)
searchHistory = new SearchHistory(0, Map())
gadt = EmptyGADTMap
}

@sharable object NoContext extends Context {
val base = null
override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this)
override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null, this)
}

/** A context base defines state and associated methods that exist once per
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2014,7 +2014,7 @@ object Types {
/** A reference to an implicit definition. This can be either a TermRef or a
* Implicits.RenamedImplicitRef.
*/
trait ImplicitRef {
trait ImplicitRef extends printing.Showable {
def implicitName(implicit ctx: Context): TermName
def underlyingRef: TermRef
}
Expand Down
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,21 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
}.close // todo: override in refined printer


def toText(ref: RenamedImplicitRef): Text =
"RenamedImplicitRef(" ~ toText(ref.underlyingRef) ~ ", " ~ toText(ref.alias) ~ ")"

// This is only in Printer to have access to a Context
def toText(irefs: ImplicitRefs): Text = irefs match {
case irefs: OfTypeImplicits =>
s"""OfTypeImplicits(${irefs.tp.show}):
| companions = ${irefs.companionRefs.toList.map(_.show).mkString(", ")}
| refs = ${irefs.refs.map(_.show).mkString(", ")}.""".stripMargin
case irefs: ContextualImplicits =>
val own = s"ContextualImplicits: ${irefs.refs.map(_.show).mkString(", ")}"
if (irefs.isOuterMost) own else own + "\n " + irefs.outerImplicits.show
}

def toText(result: SearchResult): Text = result match {
case result: SearchSuccess =>
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)
Expand Down
8 changes: 7 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import core._
import Texts._, ast.Trees._
import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant,
Names.Name, Denotations._, Annotations.Annotation
import typer.Implicits.SearchResult
import typer.Implicits.{ImplicitRefs, RenamedImplicitRef, SearchResult}
import typer.ImportInfo

/** The base class of all printers
Expand Down Expand Up @@ -97,6 +97,12 @@ abstract class Printer {
/** Textual representation of tree */
def toText[T >: Untyped](tree: Tree[T]): Text

/** Textual representation of a renamed implicit reference */
def toText(irefs: RenamedImplicitRef): Text

/** Textual representation of implicit references */
def toText(irefs: ImplicitRefs): Text

/** Textual representation of implicit search result */
def toText(result: SearchResult): Text

Expand Down
106 changes: 55 additions & 51 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ object Implicits {
/** An implicit definition `implicitRef` that is visible under a different name, `alias`.
* Gets generated if an implicit ref is imported via a renaming import.
*/
class RenamedImplicitRef(val underlyingRef: TermRef, val alias: TermName) extends ImplicitRef {
class RenamedImplicitRef(val underlyingRef: TermRef, val alias: TermName) extends ImplicitRef with Showable {
def implicitName(implicit ctx: Context): TermName = alias

def toText(printer: Printer): Text = printer.toText(this)
}

/** An eligible implicit candidate, consisting of an implicit reference and a nesting level */
Expand All @@ -61,15 +63,12 @@ object Implicits {
/** A common base class of contextual implicits and of-type implicits which
* represents a set of references to implicit definitions.
*/
abstract class ImplicitRefs(initctx: Context) {
implicit val ctx: Context =
if (initctx == NoContext) initctx else initctx retractMode Mode.ImplicitsEnabled

abstract class ImplicitRefs extends Showable {
/** The nesting level of this context. Non-zero only in ContextialImplicits */
def level: Int = 0

/** The implicit references */
def refs: List[ImplicitRef]
def refs(implicit ctx: Context): List[ImplicitRef]

/** Return those references in `refs` that are compatible with type `pt`. */
protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") {
Expand Down Expand Up @@ -146,71 +145,85 @@ object Implicits {

if (refs.isEmpty) Nil
else {
val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext)
val nestedCtx = ctx.fresh
.retractMode(Mode.ImplicitsEnabled)
.addMode(Mode.TypevarsMissContext)
refs
.filter(ref => nestedCtx.typerState.test(refMatches(ref.underlyingRef)(nestedCtx)))
.map(Candidate(_, level))
}
}

def toText(printer: Printer): Text = printer.toText(this)
}

/** The implicit references coming from the implicit scope of a type.
* @param tp the type determining the implicit scope
* @param companionRefs the companion objects in the implicit scope.
*/
class OfTypeImplicits(tp: Type, val companionRefs: TermRefSet)(initctx: Context) extends ImplicitRefs(initctx) {
assert(initctx.typer != null)
lazy val refs: List[ImplicitRef] = {
val buf = new mutable.ListBuffer[TermRef]
for (companion <- companionRefs) buf ++= companion.implicitMembers
buf.toList
class OfTypeImplicits(val tp: Type, val companionRefs: TermRefSet) extends ImplicitRefs {
private[this] var myRefs: List[TermRef] = _
override def refs(implicit ctx: Context): List[ImplicitRef] = {
if (myRefs == null) {
val buf = new mutable.ListBuffer[TermRef]
for (companion <- companionRefs) buf ++= companion.implicitMembers
myRefs = buf.toList
}
myRefs
}

private[this] var myEligible: List[Candidate] = _
/** The candidates that are eligible for expected type `tp` */
lazy val eligible: List[Candidate] =
/*>|>*/ track("eligible in tpe") /*<|<*/ {
/*>|>*/ trace(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ {
if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length)
filterMatching(tp)
def eligible(implicit ctx: Context): List[Candidate] = {
if (myEligible == null) {
/*>|>*/ track("eligible in tpe") /*<|<*/ {
/*>|>*/ trace(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ {
if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length)
myEligible = filterMatching(tp)
}
}
}

override def toString =
i"OfTypeImplicits($tp), companions = ${companionRefs.toList}%, %; refs = $refs%, %."
myEligible
}
}

/** The implicit references coming from the context.
* @param refs the implicit references made visible by the current context.
* @param myRefs the implicit references made visible by the current context.
* Note: The name of the reference might be different from the name of its symbol.
* In the case of a renaming import a => b, the name of the reference is the renamed
* name, b, whereas the name of the symbol is the original name, a.
* @param outerCtx the next outer context that makes visible further implicits
*/
class ContextualImplicits(val refs: List[ImplicitRef], val outerImplicits: ContextualImplicits)(initctx: Context) extends ImplicitRefs(initctx) {
class ContextualImplicits(myRefs: List[ImplicitRef], val outerImplicits: ContextualImplicits, private val ictx: Context) extends ImplicitRefs {
private val eligibleCache = new mutable.AnyRefMap[Type, List[Candidate]]

override def refs(implicit ctx: Context) = myRefs

/** The level increases if current context has a different owner or scope than
* the context of the next-outer ImplicitRefs. This is however disabled under
* Scala2 mode, since we do not want to change the implicit disambiguation then.
*/
override val level: Int =
override val level: Int = {
implicit val ctx: Context = ictx

if (outerImplicits == null) 1
else if (ctx.scala2Mode ||
(ctx.owner eq outerImplicits.ctx.owner) &&
(ctx.scope eq outerImplicits.ctx.scope) &&
else if (ictx.scala2Mode ||
(ictx.owner eq outerImplicits.ictx.owner) &&
(ictx.scope eq outerImplicits.ictx.scope) &&
!refs.head.implicitName.is(LazyImplicitName)) outerImplicits.level
else outerImplicits.level + 1
}

/** Is this the outermost implicits? This is the case if it either the implicits
* of NoContext, or the last one before it.
*/
private def isOuterMost = {
def isOuterMost = {
val finalImplicits = NoContext.implicits
(this eq finalImplicits) || (outerImplicits eq finalImplicits)
}

/** The implicit references that are eligible for type `tp`. */
def eligible(tp: Type): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ {
def eligible(tp: Type)(implicit ctx: Context): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ {
if (tp.hash == NotCached) computeEligible(tp)
else eligibleCache get tp match {
case Some(eligibles) =>
Expand All @@ -222,7 +235,7 @@ object Implicits {
if (monitored) record(s"elided eligible refs", elided(this))
eligibles
case None =>
if (ctx eq NoContext) Nil
if (ictx eq NoContext) Nil
else {
val savedEphemeral = ctx.typerState.ephemeral
ctx.typerState.ephemeral = false
Expand All @@ -236,7 +249,7 @@ object Implicits {
}
}

private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
private def computeEligible(tp: Type)(implicit ctx: Context): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
if (monitored) record(s"check eligible refs in ctx", refs.length)
val ownEligible = filterMatching(tp)
if (isOuterMost) ownEligible
Expand All @@ -246,21 +259,19 @@ object Implicits {
}
}

override def toString = {
val own = i"(implicits: $refs%, %)"
if (isOuterMost) own else own + "\n " + outerImplicits
}

/** This context, or a copy, ensuring root import from symbol `root`
* is not present in outer implicits.
*/
def exclude(root: Symbol): ContextualImplicits =
if (this == NoContext.implicits) this
else {
val outerExcluded = outerImplicits exclude root
if (ctx.importInfo.site.termSymbol == root) outerExcluded

implicit val ctx: Context = ictx

if (ictx.importInfo.site.termSymbol == root) outerExcluded
else if (outerExcluded eq outerImplicits) this
else new ContextualImplicits(refs, outerExcluded)(ctx)
else new ContextualImplicits(myRefs, outerExcluded, ictx)
}
}

Expand Down Expand Up @@ -376,14 +387,8 @@ trait ImplicitRunInfo { self: RunInfo =>

private val implicitScopeCache = mutable.AnyRefMap[Type, OfTypeImplicits]()

/** The implicit scope of a type `tp`
* @param liftingCtx A context to be used when computing the class symbols of
* a type. Types may contain type variables with their instances
* recorded in the current context. To find out the instance of
* a type variable, we need the current context, the current
* runinfo context does not do.
*/
def implicitScope(rootTp: Type, liftingCtx: Context): OfTypeImplicits = {
/** The implicit scope of a type `tp` */
def implicitScope(rootTp: Type)(implicit ctx: Context): OfTypeImplicits = {

val seen: mutable.Set[Type] = mutable.Set()
val incomplete: mutable.Set[Type] = mutable.Set()
Expand All @@ -395,7 +400,6 @@ trait ImplicitRunInfo { self: RunInfo =>
* abstract types are eliminated.
*/
object liftToClasses extends TypeMap {
override implicit protected val ctx: Context = liftingCtx
override def stopAtStatic = true
def apply(tp: Type) = tp match {
case tp: TypeRef if tp.symbol.isAbstractOrAliasType =>
Expand Down Expand Up @@ -458,7 +462,7 @@ trait ImplicitRunInfo { self: RunInfo =>
if (companion.exists) addRef(companion.termRef)
cls.classParents foreach addParentScope
}
tp.classSymbols(liftingCtx) foreach addClassScope
tp.classSymbols foreach addClassScope
case _ =>
// We exclude lower bounds to conform to SLS 7.2:
// "The parts of a type T are: [...] if T is an abstract type, the parts of its upper bound"
Expand All @@ -484,7 +488,7 @@ trait ImplicitRunInfo { self: RunInfo =>
iscope(liftedTp, isLifted = true).companionRefs
else
collectCompanions(tp)
val result = new OfTypeImplicits(tp, refs)(ctx)
val result = new OfTypeImplicits(tp, refs)
if (ctx.typerState.ephemeral)
record("ephemeral cache miss: implicitScope")
else if (canCache &&
Expand Down Expand Up @@ -613,7 +617,7 @@ trait Implicits { self: Typer =>
def lazyImplicitCtx(lazyImplicit: Symbol): Context = {
val lctx = ctx.fresh
for (delayedRef <- ctx.property(DelayedImplicit))
lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx))
lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits, ctx))
lctx.setProperty(DelayedImplicit, lazyImplicit.termRef)
}

Expand Down Expand Up @@ -1055,7 +1059,7 @@ trait Implicits { self: Typer =>
}
}

def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp, ctx)
def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp)
}
}

Expand Down