Skip to content

Commit afefb9e

Browse files
committed
Introduce tracked class parameters
For a tracked class parameter we add a refinement in the constructor type that the class member is the same as the parameter. E.g. ```scala class C { type T } class D(tracked val x: C) { type T = x.T } ``` This will generate the constructor type: ```scala (x1: C): D { val x: x1.type } ``` Without `tracked` the refinement would not be added. This can solve several problems with dependent class types where previously we lost track of type dependencies.
1 parent fbe97a9 commit afefb9e

38 files changed

+729
-58
lines changed

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -443,13 +443,13 @@ object desugar {
443443
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = {
444444
var mods = tparam.rawMods
445445
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
446-
tparam.withMods(mods & (EmptyFlags | Sealed) | Param)
446+
tparam.withMods(mods & EmptyFlags | Param)
447447
}
448448
private def toDefParam(vparam: ValDef, keepAnnotations: Boolean, keepDefault: Boolean): ValDef = {
449449
var mods = vparam.rawMods
450450
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
451451
val hasDefault = if keepDefault then HasDefault else EmptyFlags
452-
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault) | Param)
452+
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
453453
}
454454

455455
def mkApply(fn: Tree, paramss: List[ParamClause])(using Context): Tree =
@@ -535,7 +535,7 @@ object desugar {
535535
// but not on the constructor parameters. The reverse is true for
536536
// annotations on class _value_ parameters.
537537
val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false))
538-
val constrVparamss =
538+
def defVparamss =
539539
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
540540
if (isCaseClass)
541541
report.error(CaseClassMissingParamList(cdef), namePos)
@@ -546,6 +546,10 @@ object desugar {
546546
ListOfNil
547547
}
548548
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = true, keepDefault = true))
549+
val constrVparamss = defVparamss
550+
// defVparamss also needed as separate tree nodes in implicitWrappers below.
551+
// Need to be separate because they are `watch`ed in addParamRefinements.
552+
// See parsercombinators-givens.scala for a test case.
549553
val derivedTparams =
550554
constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) =>
551555
derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations))
@@ -623,6 +627,11 @@ object desugar {
623627
case _ => false
624628
}
625629

630+
def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match {
631+
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
632+
case _ => false
633+
}
634+
626635
def appliedRef(tycon: Tree, tparams: List[TypeDef] = constrTparams, widenHK: Boolean = false) = {
627636
val targs = for (tparam <- tparams) yield {
628637
val targ = refOfDef(tparam)
@@ -639,10 +648,13 @@ object desugar {
639648
appliedTypeTree(tycon, targs)
640649
}
641650

642-
def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match {
643-
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
644-
case _ => false
645-
}
651+
def addParamRefinements(core: Tree, paramss: List[List[ValDef]]): Tree =
652+
val refinements =
653+
for params <- paramss; param <- params; if param.mods.is(Tracked) yield
654+
ValDef(param.name, SingletonTypeTree(TermRefTree().watching(param)), EmptyTree)
655+
.withSpan(param.span)
656+
if refinements.isEmpty then core
657+
else RefinedTypeTree(core, refinements).showing(i"refined result: $result", Printers.desugar)
646658

647659
// a reference to the class type bound by `cdef`, with type parameters coming from the constructor
648660
val classTypeRef = appliedRef(classTycon)
@@ -863,18 +875,17 @@ object desugar {
863875
Nil
864876
}
865877
else {
866-
val defParamss = constrVparamss match {
878+
val defParamss = defVparamss match
867879
case Nil :: paramss =>
868880
paramss // drop leading () that got inserted by class
869881
// TODO: drop this once we do not silently insert empty class parameters anymore
870882
case paramss => paramss
871-
}
872883
val finalFlag = if ctx.settings.YcompileScala2Library.value then EmptyFlags else Final
873884
// implicit wrapper is typechecked in same scope as constructor, so
874885
// we can reuse the constructor parameters; no derived params are needed.
875886
DefDef(
876887
className.toTermName, joinParams(constrTparams, defParamss),
877-
classTypeRef, creatorExpr)
888+
addParamRefinements(classTypeRef, defParamss), creatorExpr)
878889
.withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag)
879890
.withSpan(cdef.span) :: Nil
880891
}
@@ -903,7 +914,9 @@ object desugar {
903914
}
904915
if mods.isAllOf(Given | Inline | Transparent) then
905916
report.error("inline given instances cannot be trasparent", cdef)
906-
val classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
917+
var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
918+
if vparamAccessors.exists(_.mods.is(Tracked)) then
919+
classMods |= Dependent
907920
cpy.TypeDef(cdef: TypeDef)(
908921
name = className,
909922
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
231231

232232
case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix)
233233

234+
case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked)
235+
234236
/** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */
235237
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
236238
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ object Flags {
242242
val (AccessorOrSealed @ _, Accessor @ _, Sealed @ _) = newFlags(11, "<accessor>", "sealed")
243243

244244
/** A mutable var, an open class */
245-
val (MutableOrOpen @ __, Mutable @ _, Open @ _) = newFlags(12, "mutable", "open")
245+
val (MutableOrOpen @ _, Mutable @ _, Open @ _) = newFlags(12, "mutable", "open")
246246

247247
/** Symbol is local to current class (i.e. private[this] or protected[this]
248248
* pre: Private or Protected are also set
@@ -377,6 +377,9 @@ object Flags {
377377
/** Symbol cannot be found as a member during typer */
378378
val (Invisible @ _, _, _) = newFlags(45, "<invisible>")
379379

380+
/** Tracked modifier for class parameter / a class with some tracked parameters */
381+
val (Tracked @ _, _, Dependent @ _) = newFlags(46, "tracked")
382+
380383
// ------------ Flags following this one are not pickled ----------------------------------
381384

382385
/** Symbol is not a member of its owner */
@@ -452,7 +455,7 @@ object Flags {
452455
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open
453456

454457
val TermSourceModifierFlags: FlagSet =
455-
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy
458+
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked
456459

457460
/** Flags representing modifiers that can appear in trees */
458461
val ModifierFlags: FlagSet =
@@ -466,7 +469,7 @@ object Flags {
466469
val FromStartFlags: FlagSet = commonFlags(
467470
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
468471
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
469-
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
472+
OuterOrCovariant, LabelOrContravariant, CaseAccessor, Tracked,
470473
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,
471474
SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy, Invisible)
472475

@@ -477,7 +480,7 @@ object Flags {
477480
*/
478481
val AfterLoadFlags: FlagSet = commonFlags(
479482
FromStartFlags, AccessFlags, Final, AccessorOrSealed,
480-
Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent)
483+
Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent, Tracked)
481484

482485
/** A value that's unstable unless complemented with a Stable flag */
483486
val UnstableValueFlags: FlagSet = Mutable | Method

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ object NamerOps:
1515
* @param ctor the constructor
1616
*/
1717
def effectiveResultType(ctor: Symbol, paramss: List[List[Symbol]])(using Context): Type =
18-
paramss match
19-
case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef))
20-
case _ => ctor.owner.typeRef
18+
val (resType, termParamss) = paramss match
19+
case TypeSymbols(tparams) :: rest =>
20+
(ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef)), rest)
21+
case _ =>
22+
(ctor.owner.typeRef, paramss)
23+
termParamss.flatten.foldLeft(resType): (rt, param) =>
24+
if param.is(Tracked) then RefinedType(rt, param.name, param.termRef)
25+
else rt
2126

2227
/** Split dependent class refinements off parent type. Add them to `refinements`,
2328
* unless it is null.

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,6 @@ trait PatternTypeConstrainer { self: TypeComparer =>
8888
}
8989
}
9090

91-
def stripRefinement(tp: Type): Type = tp match {
92-
case tp: RefinedOrRecType => stripRefinement(tp.parent)
93-
case tp => tp
94-
}
95-
9691
def tryConstrainSimplePatternType(pat: Type, scrut: Type) = {
9792
val patCls = pat.classSymbol
9893
val scrCls = scrut.classSymbol
@@ -181,14 +176,14 @@ trait PatternTypeConstrainer { self: TypeComparer =>
181176
case AndType(scrut1, scrut2) =>
182177
constrainPatternType(pat, scrut1) && constrainPatternType(pat, scrut2)
183178
case scrut: RefinedOrRecType =>
184-
constrainPatternType(pat, stripRefinement(scrut))
179+
constrainPatternType(pat, scrut.stripRefinement)
185180
case scrut => dealiasDropNonmoduleRefs(pat) match {
186181
case OrType(pat1, pat2) =>
187182
either(constrainPatternType(pat1, scrut), constrainPatternType(pat2, scrut))
188183
case AndType(pat1, pat2) =>
189184
constrainPatternType(pat1, scrut) && constrainPatternType(pat2, scrut)
190185
case pat: RefinedOrRecType =>
191-
constrainPatternType(stripRefinement(pat), scrut)
186+
constrainPatternType(pat.stripRefinement, scrut)
192187
case pat =>
193188
tryConstrainSimplePatternType(pat, scrut)
194189
|| classesMayBeCompatible && constrainUpcasted(scrut)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ object StdNames {
623623
val toString_ : N = "toString"
624624
val toTypeConstructor: N = "toTypeConstructor"
625625
val tpe : N = "tpe"
626+
val tracked: N = "tracked"
626627
val transparent : N = "transparent"
627628
val tree : N = "tree"
628629
val true_ : N = "true"

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,13 +1185,17 @@ object SymDenotations {
11851185
final def isExtensibleClass(using Context): Boolean =
11861186
isClass && !isOneOf(FinalOrModuleClass) && !isAnonymousClass
11871187

1188-
/** A symbol is effectively final if it cannot be overridden in a subclass */
1188+
/** A symbol is effectively final if it cannot be overridden */
11891189
final def isEffectivelyFinal(using Context): Boolean =
11901190
isOneOf(EffectivelyFinalFlags)
11911191
|| is(Inline, butNot = Deferred)
11921192
|| is(JavaDefinedVal, butNot = Method)
11931193
|| isConstructor
1194-
|| !owner.isExtensibleClass
1194+
|| !owner.isExtensibleClass && !is(Deferred)
1195+
// Deferred symbols can arise through parent refinements.
1196+
// For them, the overriding relationship reverses anyway, so
1197+
// being in a final class does not mean the symbol cannot be
1198+
// implemented concretely in a superclass.
11951199

11961200
/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
11971201
* is defined in Scala 3 and is neither abstract nor open.

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import TypeErasure.ErasedValueType
66
import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*
77
import Names.Name
88

9-
class TypeUtils {
9+
class TypeUtils:
1010
/** A decorator that provides methods on types
1111
* that are needed in the transformer pipeline.
1212
*/
13-
extension (self: Type) {
13+
extension (self: Type)
1414

1515
def isErasedValueType(using Context): Boolean =
1616
self.isInstanceOf[ErasedValueType]
@@ -150,5 +150,11 @@ class TypeUtils {
150150
case _ =>
151151
val cls = self.underlyingClassRef(refinementOK = false).typeSymbol
152152
cls.isTransparentClass && (!traitOnly || cls.is(Trait))
153-
}
154-
}
153+
154+
/** Strip all outer refinements off this type */
155+
def stripRefinement: Type = self match
156+
case self: RefinedOrRecType => self.parent.stripRefinement
157+
case seld => self
158+
159+
end TypeUtils
160+

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
798798
if (flags.is(Exported)) writeModTag(EXPORTED)
799799
if (flags.is(Given)) writeModTag(GIVEN)
800800
if (flags.is(Implicit)) writeModTag(IMPLICIT)
801+
if (flags.is(Tracked)) writeModTag(TRACKED)
801802
if (isTerm) {
802803
if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY)
803804
if (flags.is(AbsOverride)) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) }

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ class TreeUnpickler(reader: TastyReader,
737737
case INVISIBLE => addFlag(Invisible)
738738
case TRANSPARENT => addFlag(Transparent)
739739
case INFIX => addFlag(Infix)
740+
case TRACKED => addFlag(Tracked)
740741
case PRIVATEqualified =>
741742
readByte()
742743
privateWithin = readWithin

0 commit comments

Comments
 (0)