Skip to content

Commit a48fc80

Browse files
committed
Better handling of Contexts in RunInfo and Implicits
In several situations, instead of the common pattern in the compiler of using the enclosing Context available coming an implicit method parameter, we used some other Context. So far, this wasn't an issue but starting with the next commit it is important for FunProto#typedArgs to be called in a Context whose owner chain contains the Context where the FunProto was created. This motivated the following changes: - RunInfo does not actually need to keep a reference to the root Context: It was previously used by `ImplicitRunInfo#implicitScope`, but can be replaced by using the Context from the enclosing scope (which was already used in this method with the name `liftingCtx`). - OfTypeImplicits does not need to keep a reference to the root Context either, we can just replace its lazy vals by defs that cache their results. - ContextualImplicits does need to keep track of the Context from which its implicit references come from, but this Context itself does not have to be available implicitly in the class, instead the Context from the enclosing scope is used. This also requires replacing OfTypeImplicits#toString and ContextualImplicits#toString by a toText method in Printer to get access to a Context.
1 parent 9fd4d31 commit a48fc80

File tree

5 files changed

+72
-60
lines changed

5 files changed

+72
-60
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Compiler {
141141
ctx.initialize()(start) // re-initialize the base context with start
142142
def addImport(ctx: Context, refFn: () => TermRef) =
143143
ctx.fresh.setImportInfo(ImportInfo.rootImport(refFn)(ctx))
144-
(start.setRunInfo(new RunInfo(start)) /: defn.RootImportFns)(addImport)
144+
(start.setRunInfo(new RunInfo) /: defn.RootImportFns)(addImport)
145145
}
146146

147147
def reset()(implicit ctx: Context): Unit = {

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ object Contexts {
219219
else
220220
outer.implicits
221221
if (implicitRefs.isEmpty) outerImplicits
222-
else new ContextualImplicits(implicitRefs, outerImplicits)(this)
222+
else new ContextualImplicits(implicitRefs, outerImplicits, this)
223223
}
224224
implicitsCache
225225
}
@@ -522,7 +522,7 @@ object Contexts {
522522
sstate = settings.defaultState
523523
tree = untpd.EmptyTree
524524
typeAssigner = TypeAssigner
525-
runInfo = new RunInfo(this)
525+
runInfo = new RunInfo
526526
diagnostics = None
527527
freshNames = new FreshNameCreator.Default
528528
moreProperties = Map.empty
@@ -533,7 +533,7 @@ object Contexts {
533533

534534
@sharable object NoContext extends Context {
535535
val base = null
536-
override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this)
536+
override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null, this)
537537
}
538538

539539
/** A context base defines state and associated methods that exist once per
@@ -695,9 +695,7 @@ object Contexts {
695695
}
696696

697697
/** Info that changes on each compiler run */
698-
class RunInfo(initctx: Context) extends ImplicitRunInfo with ConstraintRunInfo {
699-
implicit val ctx: Context = initctx
700-
}
698+
class RunInfo extends ImplicitRunInfo with ConstraintRunInfo
701699

702700
class GADTMap(initBounds: SimpleMap[Symbol, TypeBounds]) {
703701
private var myBounds = initBounds

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,18 @@ class PlainPrinter(_ctx: Context) extends Printer {
491491
}
492492
}.close // todo: override in refined printer
493493

494+
495+
// This is only in Printer to have access to a Context
496+
def toText(irefs: ImplicitRefs): Text = irefs match {
497+
case irefs: OfTypeImplicits =>
498+
s"""OfTypeImplicits(${irefs.tp.show}):
499+
| companions = ${irefs.companionRefs.toList.map(_.show).mkString(", ")}
500+
| refs = ${irefs.refs.map(_.show).mkString(", ")}.""".stripMargin
501+
case irefs: ContextualImplicits =>
502+
val own = s"ContextualImplicits: ${irefs.refs.map(_.show).mkString(", ")}"
503+
if (irefs.isOuterMost) own else own + "\n " + irefs.outerImplicits.show
504+
}
505+
494506
def toText(result: SearchResult): Text = result match {
495507
case result: SearchSuccess =>
496508
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)

compiler/src/dotty/tools/dotc/printing/Printer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import core._
55
import Texts._, ast.Trees._
66
import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant,
77
Names.Name, Denotations._, Annotations.Annotation
8-
import typer.Implicits.SearchResult
8+
import typer.Implicits.{ImplicitRefs, SearchResult}
99
import typer.ImportInfo
1010

1111
/** The base class of all printers
@@ -96,6 +96,9 @@ abstract class Printer {
9696
/** Textual representation of tree */
9797
def toText[T >: Untyped](tree: Tree[T]): Text
9898

99+
/** Textual representation of implicit references */
100+
def toText(irefs: ImplicitRefs): Text
101+
99102
/** Textual representation of implicit search result */
100103
def toText(result: SearchResult): Text
101104

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

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,12 @@ object Implicits {
4848
/** A common base class of contextual implicits and of-type implicits which
4949
* represents a set of implicit references.
5050
*/
51-
abstract class ImplicitRefs(initctx: Context) {
52-
implicit val ctx: Context =
53-
if (initctx == NoContext) initctx else initctx retractMode Mode.ImplicitsEnabled
54-
51+
abstract class ImplicitRefs extends Showable {
5552
/** The nesting level of this context. Non-zero only in ContextialImplicits */
5653
def level: Int = 0
5754

5855
/** The implicit references */
59-
def refs: List[TermRef]
56+
def refs(implicit ctx: Context): List[TermRef]
6057

6158
/** Return those references in `refs` that are compatible with type `pt`. */
6259
protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") {
@@ -131,67 +128,80 @@ object Implicits {
131128
}
132129

133130
if (refs.isEmpty) Nil
134-
else refs.filter(refMatches(_)(ctx.fresh.addMode(Mode.TypevarsMissContext).setExploreTyperState)) // create a defensive copy of ctx to avoid constraint pollution
135-
.map(Candidate(_, level))
131+
else {
132+
val filteringCtx = ctx.fresh
133+
.retractMode(Mode.ImplicitsEnabled)
134+
.addMode(Mode.TypevarsMissContext)
135+
.setExploreTyperState // create a defensive copy of ctx to avoid constraint pollution
136+
refs.filter(refMatches(_)(filteringCtx)).map(Candidate(_, level))
137+
}
136138
}
139+
140+
def toText(printer: Printer): Text = printer.toText(this)
137141
}
138142

139143
/** The implicit references coming from the implicit scope of a type.
140144
* @param tp the type determining the implicit scope
141145
* @param companionRefs the companion objects in the implicit scope.
142146
*/
143-
class OfTypeImplicits(tp: Type, val companionRefs: TermRefSet)(initctx: Context) extends ImplicitRefs(initctx) {
144-
assert(initctx.typer != null)
145-
lazy val refs: List[TermRef] = {
146-
val buf = new mutable.ListBuffer[TermRef]
147-
for (companion <- companionRefs) buf ++= companion.implicitMembers
148-
buf.toList
147+
class OfTypeImplicits(val tp: Type, val companionRefs: TermRefSet) extends ImplicitRefs {
148+
private[this] var myRefs: List[TermRef] = _
149+
def refs(implicit ctx: Context): List[TermRef] = {
150+
if (myRefs == null) {
151+
val buf = new mutable.ListBuffer[TermRef]
152+
for (companion <- companionRefs) buf ++= companion.implicitMembers
153+
myRefs = buf.toList
154+
}
155+
myRefs
149156
}
150157

158+
private[this] var myEligible: List[Candidate] = _
151159
/** The candidates that are eligible for expected type `tp` */
152-
lazy val eligible: List[Candidate] =
153-
/*>|>*/ track("eligible in tpe") /*<|<*/ {
154-
/*>|>*/ ctx.traceIndented(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ {
155-
if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length)
156-
filterMatching(tp)
160+
def eligible(implicit ctx: Context): List[Candidate] = {
161+
if (myEligible == null) {
162+
/*>|>*/ track("eligible in tpe") /*<|<*/ {
163+
/*>|>*/ ctx.traceIndented(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ {
164+
if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length)
165+
myEligible = filterMatching(tp)
166+
}
157167
}
158168
}
159-
160-
override def toString =
161-
i"OfTypeImplicits($tp), companions = ${companionRefs.toList}%, %; refs = $refs%, %."
169+
myEligible
170+
}
162171
}
163172

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

182+
override def refs(implicit ctx: Context) = myRefs
183+
174184
/** The level increases if current context has a different owner or scope than
175185
* the context of the next-outer ImplicitRefs. This is however disabled under
176186
* Scala2 mode, since we do not want to change the implicit disambiguation then.
177187
*/
178188
override val level: Int =
179189
if (outerImplicits == null) 1
180-
else if (ctx.scala2Mode ||
181-
(ctx.owner eq outerImplicits.ctx.owner) &&
182-
(ctx.scope eq outerImplicits.ctx.scope)) outerImplicits.level
190+
else if (ictx.scala2Mode ||
191+
(ictx.owner eq outerImplicits.ictx.owner) &&
192+
(ictx.scope eq outerImplicits.ictx.scope)) outerImplicits.level
183193
else outerImplicits.level + 1
184194

185195
/** Is this the outermost implicits? This is the case if it either the implicits
186196
* of NoContext, or the last one before it.
187197
*/
188-
private def isOuterMost = {
198+
def isOuterMost = {
189199
val finalImplicits = NoContext.implicits
190200
(this eq finalImplicits) || (outerImplicits eq finalImplicits)
191201
}
192202

193203
/** The implicit references that are eligible for type `tp`. */
194-
def eligible(tp: Type): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ {
204+
def eligible(tp: Type)(implicit ctx: Context): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ {
195205
if (tp.hash == NotCached) computeEligible(tp)
196206
else eligibleCache get tp match {
197207
case Some(eligibles) =>
@@ -203,7 +213,7 @@ object Implicits {
203213
if (monitored) record(s"elided eligible refs", elided(this))
204214
eligibles
205215
case None =>
206-
if (ctx eq NoContext) Nil
216+
if (ictx eq NoContext) Nil
207217
else {
208218
val savedEphemeral = ctx.typerState.ephemeral
209219
ctx.typerState.ephemeral = false
@@ -217,8 +227,8 @@ object Implicits {
217227
}
218228
}
219229

220-
private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ ctx.traceIndented(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
221-
if (monitored) record(s"check eligible refs in ctx", refs.length)
230+
private def computeEligible(tp: Type)(implicit ctx: Context): List[Candidate] = /*>|>*/ ctx.traceIndented(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
231+
if (monitored) record(s"check eligible refs in ictx", refs.length)
222232
val ownEligible = filterMatching(tp)
223233
if (isOuterMost) ownEligible
224234
else ownEligible ::: {
@@ -227,21 +237,16 @@ object Implicits {
227237
}
228238
}
229239

230-
override def toString = {
231-
val own = i"(implicits: $refs%, %)"
232-
if (isOuterMost) own else own + "\n " + outerImplicits
233-
}
234-
235240
/** This context, or a copy, ensuring root import from symbol `root`
236241
* is not present in outer implicits.
237242
*/
238243
def exclude(root: Symbol): ContextualImplicits =
239244
if (this == NoContext.implicits) this
240245
else {
241246
val outerExcluded = outerImplicits exclude root
242-
if (ctx.importInfo.site.termSymbol == root) outerExcluded
247+
if (ictx.importInfo.site(ictx).termSymbol(ictx) == root) outerExcluded
243248
else if (outerExcluded eq outerImplicits) this
244-
else new ContextualImplicits(refs, outerExcluded)(ctx)
249+
else new ContextualImplicits(myRefs, outerExcluded, ictx)
245250
}
246251
}
247252

@@ -355,14 +360,8 @@ trait ImplicitRunInfo { self: RunInfo =>
355360

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

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

367366
val seen: mutable.Set[Type] = mutable.Set()
368367
val incomplete: mutable.Set[Type] = mutable.Set()
@@ -374,7 +373,6 @@ trait ImplicitRunInfo { self: RunInfo =>
374373
* abstract types are eliminated.
375374
*/
376375
object liftToClasses extends TypeMap {
377-
override implicit protected val ctx: Context = liftingCtx
378376
override def stopAtStatic = true
379377
def apply(tp: Type) = tp match {
380378
case tp: TypeRef if tp.symbol.isAbstractOrAliasType =>
@@ -440,7 +438,7 @@ trait ImplicitRunInfo { self: RunInfo =>
440438
if (companion.exists) addRef(companion.valRef)
441439
cls.classParents foreach addParentScope
442440
}
443-
tp.classSymbols(liftingCtx) foreach addClassScope
441+
tp.classSymbols foreach addClassScope
444442
case _ =>
445443
// We exclude lower bounds to conform to SLS 7.2:
446444
// "The parts of a type T are: [...] if T is an abstract type, the parts of its upper bound"
@@ -466,7 +464,8 @@ trait ImplicitRunInfo { self: RunInfo =>
466464
iscope(liftedTp, isLifted = true).companionRefs
467465
else
468466
collectCompanions(tp)
469-
val result = new OfTypeImplicits(tp, refs)(ctx)
467+
val result = new OfTypeImplicits(tp, refs)
468+
470469
if (ctx.typerState.ephemeral)
471470
record("ephemeral cache miss: implicitScope")
472471
else if (canCache &&
@@ -600,7 +599,7 @@ trait Implicits { self: Typer =>
600599
def lazyImplicitCtx(lazyImplicit: Symbol): Context = {
601600
val lctx = ctx.fresh
602601
for (delayedRef <- ctx.property(DelayedImplicit))
603-
lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx))
602+
lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits, ctx))
604603
lctx.setProperty(DelayedImplicit, lazyImplicit.termRef)
605604
}
606605

@@ -900,7 +899,7 @@ trait Implicits { self: Typer =>
900899
}
901900
}
902901

903-
def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp, ctx)
902+
def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp)
904903
}
905904

906905
final class ExplainedImplicitSearch(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context)

0 commit comments

Comments
 (0)