Skip to content

Commit 7d754d6

Browse files
committed
Refinements for context-bound companions
Add a config setting whether or not to use the type name for unary context bounds as default name. It's on by default. If it is off, context bound companions are created instead. After fixing several problems, the test suite was verified to compile with the setting set to off.
1 parent 495072a commit 7d754d6

File tree

8 files changed

+125
-94
lines changed

8 files changed

+125
-94
lines changed

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

+52-27
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Annotations.Annotation
1010
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
1111
import typer.{Namer, Checking}
1212
import util.{Property, SourceFile, SourcePosition, Chars}
13-
import config.Feature
13+
import config.{Feature, Config}
1414
import config.SourceVersion.*
1515
import collection.mutable.ListBuffer
1616
import reporting.*
@@ -230,7 +230,8 @@ object desugar {
230230
tdef: TypeDef,
231231
evidenceBuf: ListBuffer[ValDef],
232232
flags: FlagSet,
233-
freshName: => TermName)(using Context): TypeDef =
233+
freshName: untpd.Tree => TermName,
234+
allParamss: List[ParamClause])(using Context): TypeDef =
234235

235236
val evidenceNames = ListBuffer[TermName]()
236237

@@ -241,11 +242,11 @@ object desugar {
241242
val evidenceName = bound match
242243
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty =>
243244
ownName
244-
case _ if !isMember && cxbounds.tail.isEmpty && Feature.enabled(Feature.modularity) =>
245+
case _ if !isMember && cxbounds.tail.isEmpty
246+
&& Feature.enabled(Feature.modularity) && Config.nameSingleContextBounds =>
245247
tdef.name.toTermName
246248
case _ =>
247-
if isMember then inventGivenOrExtensionName(bound)
248-
else freshName
249+
freshName(bound)
249250
evidenceNames += evidenceName
250251
val evidenceParam = ValDef(evidenceName, bound, EmptyTree).withFlags(flags)
251252
evidenceParam.pushAttachment(ContextBoundParam, ())
@@ -257,9 +258,13 @@ object desugar {
257258
rhs
258259

259260
val tdef1 = cpy.TypeDef(tdef)(rhs = desugarRhs(tdef.rhs))
260-
if evidenceNames.nonEmpty && !evidenceNames.contains(tdef.name.toTermName) then
261-
val witnessNamesAnnot = WitnessNamesAnnot(evidenceNames.toList).withSpan(tdef.span)
262-
tdef1.withAddedAnnotation(witnessNamesAnnot)
261+
if Feature.enabled(Feature.modularity)
262+
&& evidenceNames.nonEmpty
263+
&& !evidenceNames.contains(tdef.name.toTermName)
264+
&& !allParamss.nestedExists(_.name == tdef.name.toTermName)
265+
then
266+
tdef1.withAddedAnnotation:
267+
WitnessNamesAnnot(evidenceNames.toList).withSpan(tdef.span)
263268
else
264269
tdef1
265270
end desugarContextBounds
@@ -268,7 +273,7 @@ object desugar {
268273
val DefDef(_, paramss, tpt, rhs) = meth
269274
val evidenceParamBuf = ListBuffer[ValDef]()
270275
var seenContextBounds: Int = 0
271-
def freshName =
276+
def freshName(unused: Tree) =
272277
seenContextBounds += 1 // Start at 1 like FreshNameCreator.
273278
ContextBoundParamName(EmptyTermName, seenContextBounds)
274279
// Just like with `makeSyntheticParameter` on nameless parameters of
@@ -280,7 +285,7 @@ object desugar {
280285
val iflag = if Feature.sourceVersion.isAtLeast(`future`) then Given else Implicit
281286
val flags = if isPrimaryConstructor then iflag | LocalParamAccessor else iflag | Param
282287
mapParamss(paramss) {
283-
tparam => desugarContextBounds(tparam, evidenceParamBuf, flags, freshName)
288+
tparam => desugarContextBounds(tparam, evidenceParamBuf, flags, freshName, paramss)
284289
}(identity)
285290

286291
rhs match
@@ -326,9 +331,9 @@ object desugar {
326331

327332
def getterParamss(n: Int): List[ParamClause] =
328333
mapParamss(takeUpTo(paramssNoRHS, n)) {
329-
tparam => dropContextBounds(toDefParam(tparam, keepAnnotations = true))
334+
tparam => dropContextBounds(toDefParam(tparam, KeepAnnotations.All))
330335
} {
331-
vparam => toDefParam(vparam, keepAnnotations = true, keepDefault = false)
336+
vparam => toDefParam(vparam, KeepAnnotations.All, keepDefault = false)
332337
}
333338

334339
def defaultGetters(paramss: List[ParamClause], n: Int): List[DefDef] = paramss match
@@ -433,7 +438,14 @@ object desugar {
433438
private def addEvidenceParams(meth: DefDef, params: List[ValDef])(using Context): DefDef =
434439
if params.isEmpty then return meth
435440

436-
val boundNames = params.map(_.name).toSet
441+
var boundNames = params.map(_.name).toSet
442+
for mparams <- meth.paramss; mparam <- mparams do
443+
mparam match
444+
case tparam: TypeDef if tparam.mods.annotations.exists(WitnessNamesAnnot.unapply(_).isDefined) =>
445+
boundNames += tparam.name.toTermName
446+
case _ =>
447+
448+
//println(i"add ev params ${meth.name}, ${boundNames.toList}")
437449

438450
def references(vdef: ValDef): Boolean =
439451
vdef.tpt.existsSubTree:
@@ -464,15 +476,26 @@ object desugar {
464476

465477
@sharable private val synthetic = Modifiers(Synthetic)
466478

467-
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = {
468-
var mods = tparam.rawMods
469-
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
479+
/** Which annotations to keep in derived parameters */
480+
private enum KeepAnnotations:
481+
case None, All, WitnessOnly
482+
483+
/** Filter annotations in `mods` according to `keep` */
484+
private def filterAnnots(mods: Modifiers, keep: KeepAnnotations)(using Context) = keep match
485+
case KeepAnnotations.None => mods.withAnnotations(Nil)
486+
case KeepAnnotations.All => mods
487+
case KeepAnnotations.WitnessOnly =>
488+
mods.withAnnotations:
489+
mods.annotations.filter:
490+
case WitnessNamesAnnot(_) => true
491+
case _ => false
492+
493+
private def toDefParam(tparam: TypeDef, keep: KeepAnnotations)(using Context): TypeDef =
494+
val mods = filterAnnots(tparam.rawMods, keep)
470495
tparam.withMods(mods & EmptyFlags | Param)
471-
}
472496

473-
private def toDefParam(vparam: ValDef, keepAnnotations: Boolean, keepDefault: Boolean)(using Context): ValDef = {
474-
var mods = vparam.rawMods
475-
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
497+
private def toDefParam(vparam: ValDef, keep: KeepAnnotations, keepDefault: Boolean)(using Context): ValDef = {
498+
val mods = filterAnnots(vparam.rawMods, keep)
476499
val hasDefault = if keepDefault then HasDefault else EmptyFlags
477500
// Need to ensure that tree is duplicated since term parameters can be watched
478501
// and cloning a term parameter will copy its watchers to the clone, which means
@@ -495,8 +518,10 @@ object desugar {
495518

496519
def typeDef(tdef: TypeDef)(using Context): Tree =
497520
val evidenceBuf = new ListBuffer[ValDef]
498-
val result = desugarContextBounds(tdef, evidenceBuf,
499-
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags, EmptyTermName)
521+
val result = desugarContextBounds(
522+
tdef, evidenceBuf,
523+
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
524+
inventGivenOrExtensionName, Nil)
500525
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
501526

502527
/** The expansion of a class definition. See inline comments for what is involved */
@@ -571,7 +596,7 @@ object desugar {
571596
// Annotations on class _type_ parameters are set on the derived parameters
572597
// but not on the constructor parameters. The reverse is true for
573598
// annotations on class _value_ parameters.
574-
val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false))
599+
val constrTparams = impliedTparams.map(toDefParam(_, KeepAnnotations.WitnessOnly))
575600
def defVparamss =
576601
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
577602
if (isCaseClass)
@@ -582,7 +607,7 @@ object desugar {
582607
report.error(CaseClassMissingNonImplicitParamList(cdef), namePos)
583608
ListOfNil
584609
}
585-
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = true, keepDefault = true))
610+
else originalVparamss.nestedMap(toDefParam(_, KeepAnnotations.All, keepDefault = true))
586611
val constrVparamss = defVparamss
587612
// defVparamss also needed as separate tree nodes in implicitWrappers below.
588613
// Need to be separate because they are `watch`ed in addParamRefinements.
@@ -608,7 +633,7 @@ object desugar {
608633
defDef(
609634
addEvidenceParams(
610635
cpy.DefDef(ddef)(paramss = joinParams(constrTparams, ddef.paramss)),
611-
evidenceParams(constr1).map(toDefParam(_, keepAnnotations = false, keepDefault = false)))))
636+
evidenceParams(constr1).map(toDefParam(_, KeepAnnotations.None, keepDefault = false)))))
612637
case stat =>
613638
stat
614639
}
@@ -914,9 +939,9 @@ object desugar {
914939
}
915940
else {
916941
val defParamss = defVparamss.nestedMapConserve: param =>
917-
// for named context bound parameters, we assume that they might have embedded types
942+
// for context bound parameters, we assume that they might have embedded types
918943
// so they should be treated as tracked.
919-
if param.hasAttachment(ContextBoundParam) && !param.name.is(ContextBoundParamName)
944+
if param.hasAttachment(ContextBoundParam) && Feature.enabled(Feature.modularity)
920945
then param.withFlags(param.mods.flags | Tracked)
921946
else param
922947
match

compiler/src/dotty/tools/dotc/config/Config.scala

+7
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,11 @@ object Config {
235235
*/
236236
inline val checkLevelsOnConstraints = false
237237
inline val checkLevelsOnInstantiation = true
238+
239+
/** If a type parameter `X` has a single context bounf `X: C`, should the
240+
* witness parameter be named `X`? This would prevent the creation of a
241+
* context bound companion.
242+
*/
243+
inline val nameSingleContextBounds = true
238244
}
245+

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

+8-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Symbols.*
1212
import Scopes.*
1313
import Uniques.*
1414
import ast.Trees.*
15+
import Flags.ParamAccessor
1516
import ast.untpd
1617
import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance}
1718
import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables}
@@ -399,7 +400,8 @@ object Contexts {
399400
*
400401
* - as owner: The primary constructor of the class
401402
* - as outer context: The context enclosing the class context
402-
* - as scope: The parameter accessors in the class context
403+
* - as scope: type parameters, the parameter accessors, and
404+
* the context bound companions in the class context,
403405
*
404406
* The reasons for this peculiar choice of attributes are as follows:
405407
*
@@ -413,10 +415,11 @@ object Contexts {
413415
* context see the constructor parameters instead, but then we'd need a final substitution step
414416
* from constructor parameters to class parameter accessors.
415417
*/
416-
def superCallContext: Context = {
417-
val locals = newScopeWith(owner.typeParams ++ owner.asClass.paramAccessors*)
418-
superOrThisCallContext(owner.primaryConstructor, locals)
419-
}
418+
def superCallContext: Context =
419+
val locals = owner.typeParams
420+
++ owner.asClass.unforcedDecls.filter: sym =>
421+
sym.is(ParamAccessor) || sym.isContextBoundCompanion
422+
superOrThisCallContext(owner.primaryConstructor, newScopeWith(locals*))
420423

421424
/** The context for the arguments of a this(...) constructor call.
422425
* The context is computed from the local auxiliary constructor context.

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

+22-28
Original file line numberDiff line numberDiff line change
@@ -250,48 +250,42 @@ object NamerOps:
250250
rhsCtx.gadtState.addBound(psym, tr, isUpper = true)
251251
}
252252

253-
/** Create a context-bound companion for type symbol `tsym` unless it
254-
* would clash with another parameter. `tsym` is a context-bound symbol
255-
* that defined a set of witnesses with names `witnessNames`.
253+
/** Create a context-bound companion for type symbol `tsym`, which has a context
254+
* bound that defines a set of witnesses with names `witnessNames`.
256255
*
257-
* @param paramSymss If `tsym` is a type parameter, the other parameter symbols,
258-
* including witnesses, of the method containing `tsym`.
259-
* If `tsym` is an abstract type member, `paramSymss` is the
260-
* empty list.
256+
* @param parans If `tsym` is a type parameter, a list of parameter symbols
257+
* that include all witnesses, otherwise the empty list.
261258
*
262259
* The context-bound companion has as name the name of `tsym` translated to
263260
* a term name. We create a synthetic val of the form
264261
*
265-
* val A: CBCompanion[witnessRef1 | ... | witnessRefN]
262+
* val A: `<context-bound-companion>`[witnessRef1 | ... | witnessRefN]
266263
*
267264
* where
268265
*
269-
* CBCompanion is the <context-bound-companion> type created in Definitions
270-
* withnessRefK is a refence to the K'the witness.
266+
* <context-bound-companion> is the CBCompanion type created in Definitions
267+
* withnessRefK is a refence to the K'th witness.
271268
*
272269
* The companion has the same access flags as the original type.
273270
*/
274-
def maybeAddContextBoundCompanionFor(tsym: Symbol, witnessNames: List[TermName], paramSymss: List[List[Symbol]])(using Context): Unit =
271+
def addContextBoundCompanionFor(tsym: Symbol, witnessNames: List[TermName], params: List[Symbol])(using Context): Unit =
275272
val prefix = ctx.owner.thisType
276273
val companionName = tsym.name.toTermName
277274
val witnessRefs =
278-
if paramSymss.nonEmpty then
279-
if paramSymss.nestedExists(_.name == companionName) then Nil
280-
else
281-
witnessNames.map: witnessName =>
282-
prefix.select(paramSymss.nestedFind(_.name == witnessName).get)
275+
if params.nonEmpty then
276+
witnessNames.map: witnessName =>
277+
prefix.select(params.find(_.name == witnessName).get)
283278
else
284-
witnessNames.map(prefix.select)
285-
if witnessRefs.nonEmpty then
286-
val cbtype = defn.CBCompanion.typeRef.appliedTo:
287-
witnessRefs.reduce[Type](OrType(_, _, soft = false))
288-
val cbc = newSymbol(
289-
ctx.owner, companionName,
290-
(tsym.flagsUNSAFE & AccessFlags) | Synthetic,
291-
cbtype)
292-
typr.println(i"contetx bpund companion created $cbc: $cbtype in ${ctx.owner}")
293-
ctx.enter(cbc)
294-
end maybeAddContextBoundCompanionFor
279+
witnessNames.map(TermRef(prefix, _))
280+
val cbtype = defn.CBCompanion.typeRef.appliedTo:
281+
witnessRefs.reduce[Type](OrType(_, _, soft = false))
282+
val cbc = newSymbol(
283+
ctx.owner, companionName,
284+
(tsym.flagsUNSAFE & (AccessFlags)).toTermFlags | Synthetic,
285+
cbtype)
286+
typr.println(s"context bound companion created $cbc for $witnessNames in ${ctx.owner}")
287+
ctx.enter(cbc)
288+
end addContextBoundCompanionFor
295289

296290
/** Add context bound companions to all context-bound types declared in
297291
* this class. This assumes that these types already have their
@@ -306,5 +300,5 @@ object NamerOps:
306300
if ann.symbol == defn.WitnessNamesAnnot then
307301
ann.tree match
308302
case ast.tpd.WitnessNamesAnnot(witnessNames) =>
309-
maybeAddContextBoundCompanionFor(sym, witnessNames, Nil)
303+
addContextBoundCompanionFor(sym, witnessNames, Nil)
310304
end NamerOps

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class SymUtils:
8888
}
8989

9090
def isContextBoundCompanion(using Context): Boolean =
91-
self.is(Synthetic) && self.info.typeSymbol == defn.CBCompanion
91+
self.is(Synthetic) && self.infoOrCompleter.typeSymbol == defn.CBCompanion
9292

9393
/** Is this a case class for which a product mirror is generated?
9494
* Excluded are value classes, abstract classes and case classes with more than one

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,7 @@ class TreeUnpickler(reader: TastyReader,
11241124
})
11251125
defn.patchStdLibClass(cls)
11261126
NamerOps.addConstructorProxies(cls)
1127+
NamerOps.addContextBoundCompanions(cls)
11271128
setSpan(start,
11281129
untpd.Template(constr, mappedParents, self, lazyStats)
11291130
.withType(localDummy.termRef))

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -430,11 +430,11 @@ class PlainPrinter(_ctx: Context) extends Printer {
430430
sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName
431431

432432
/** String representation of a definition's type following its name,
433-
* if symbol is completed, "?" otherwise.
433+
* if symbol is completed, ": ?" otherwise.
434434
*/
435435
protected def toTextRHS(optType: Option[Type]): Text = optType match {
436436
case Some(tp) => toTextRHS(tp)
437-
case None => "?"
437+
case None => ": ?"
438438
}
439439

440440
protected def decomposeLambdas(bounds: TypeBounds): (Text, TypeBounds) =

0 commit comments

Comments
 (0)