Skip to content

Commit 1153011

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 b38ed57 commit 1153011

38 files changed

+757
-69
lines changed

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -429,13 +429,13 @@ object desugar {
429429
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = {
430430
var mods = tparam.rawMods
431431
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
432-
tparam.withMods(mods & (EmptyFlags | Sealed) | Param)
432+
tparam.withMods(mods & EmptyFlags | Param)
433433
}
434434
private def toDefParam(vparam: ValDef, keepAnnotations: Boolean, keepDefault: Boolean): ValDef = {
435435
var mods = vparam.rawMods
436436
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
437437
val hasDefault = if keepDefault then HasDefault else EmptyFlags
438-
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault) | Param)
438+
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
439439
}
440440

441441
def mkApply(fn: Tree, paramss: List[ParamClause])(using Context): Tree =
@@ -860,9 +860,8 @@ object desugar {
860860
// implicit wrapper is typechecked in same scope as constructor, so
861861
// we can reuse the constructor parameters; no derived params are needed.
862862
DefDef(
863-
className.toTermName, joinParams(constrTparams, defParamss),
864-
classTypeRef, creatorExpr)
865-
.withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag)
863+
className.toTermName, joinParams(constrTparams, defParamss), classTypeRef, creatorExpr
864+
) .withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag)
866865
.withSpan(cdef.span) :: Nil
867866
}
868867

@@ -890,7 +889,9 @@ object desugar {
890889
}
891890
if mods.isAllOf(Given | Inline | Transparent) then
892891
report.error("inline given instances cannot be trasparent", cdef)
893-
val classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
892+
var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
893+
if vparamAccessors.exists(_.mods.is(Tracked)) then
894+
classMods |= Dependent
894895
cpy.TypeDef(cdef: TypeDef)(
895896
name = className,
896897
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
@@ -230,6 +230,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
230230

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

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

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,21 @@ object NamerOps:
1616
*/
1717
def effectiveResultType(ctor: Symbol, paramss: List[List[Symbol]])(using Context): Type =
1818
paramss match
19-
case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef))
20-
case _ => ctor.owner.typeRef
19+
case TypeSymbols(tparams) :: rest =>
20+
addParamRefinements(ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef)), rest)
21+
case _ =>
22+
addParamRefinements(ctor.owner.typeRef, paramss)
23+
24+
/** Given a method with tracked term-parameters `p1, ..., pn`, and result type `R`, add the
25+
* refinements R { p1 = p1' } ... { pn = pn' }, where pi' is the term parameter ref
26+
* of the parameter and pi is its name. This matters only under experimental.modularity,
27+
* since wothout it there are no tracked parameters. Parameter refinements are added for
28+
* constructors and given companion methods.
29+
*/
30+
def addParamRefinements(resType: Type, paramss: List[List[Symbol]])(using Context): Type =
31+
paramss.flatten.foldLeft(resType): (rt, param) =>
32+
if param.is(Tracked) then RefinedType(rt, param.name, param.termRef)
33+
else rt
2134

2235
/** Split dependent class refinements off parent type. Add them to `refinements`,
2336
* 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
@@ -625,6 +625,7 @@ object StdNames {
625625
val toString_ : N = "toString"
626626
val toTypeConstructor: N = "toTypeConstructor"
627627
val tpe : N = "tpe"
628+
val tracked: N = "tracked"
628629
val transparent : N = "transparent"
629630
val tree : N = "tree"
630631
val true_ : N = "true"

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,21 +1190,25 @@ object SymDenotations {
11901190
final def isExtensibleClass(using Context): Boolean =
11911191
isClass && !isOneOf(FinalOrModuleClass) && !isAnonymousClass
11921192

1193-
/** A symbol is effectively final if it cannot be overridden in a subclass */
1193+
/** A symbol is effectively final if it cannot be overridden */
11941194
final def isEffectivelyFinal(using Context): Boolean =
11951195
isOneOf(EffectivelyFinalFlags)
11961196
|| is(Inline, butNot = Deferred)
11971197
|| is(JavaDefinedVal, butNot = Method)
11981198
|| isConstructor
1199-
|| !owner.isExtensibleClass
1199+
|| !owner.isExtensibleClass && !is(Deferred)
1200+
// Deferred symbols can arise through parent refinements.
1201+
// For them, the overriding relationship reverses anyway, so
1202+
// being in a final class does not mean the symbol cannot be
1203+
// implemented concretely in a superclass.
12001204

12011205
/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
12021206
* is defined in Scala 3 and is neither abstract nor open.
12031207
*/
12041208
final def isEffectivelySealed(using Context): Boolean =
12051209
isOneOf(FinalOrSealed)
1206-
|| isClass && (!isOneOf(EffectivelyOpenFlags)
1207-
|| isLocalToCompilationUnit)
1210+
|| isClass
1211+
&& (!isOneOf(EffectivelyOpenFlags) || isLocalToCompilationUnit)
12081212

12091213
final def isLocalToCompilationUnit(using Context): Boolean =
12101214
is(Private)

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
@@ -807,6 +807,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
807807
if (flags.is(Exported)) writeModTag(EXPORTED)
808808
if (flags.is(Given)) writeModTag(GIVEN)
809809
if (flags.is(Implicit)) writeModTag(IMPLICIT)
810+
if (flags.is(Tracked)) writeModTag(TRACKED)
810811
if (isTerm) {
811812
if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY)
812813
if (flags.is(AbsOverride)) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) }

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import util.{SourceFile, Property}
3131
import ast.{Trees, tpd, untpd}
3232
import Trees.*
3333
import Decorators.*
34-
import dotty.tools.dotc.quoted.QuotePatterns
34+
import config.Feature
35+
import quoted.QuotePatterns
3536

3637
import dotty.tools.tasty.{TastyBuffer, TastyReader}
3738
import TastyBuffer.*
@@ -751,6 +752,7 @@ class TreeUnpickler(reader: TastyReader,
751752
case INVISIBLE => addFlag(Invisible)
752753
case TRANSPARENT => addFlag(Transparent)
753754
case INFIX => addFlag(Infix)
755+
case TRACKED => addFlag(Tracked)
754756
case PRIVATEqualified =>
755757
readByte()
756758
privateWithin = readWithin
@@ -918,6 +920,8 @@ class TreeUnpickler(reader: TastyReader,
918920
val resType =
919921
if name == nme.CONSTRUCTOR then
920922
effectiveResultType(sym, paramss)
923+
else if sym.isAllOf(Given | Method) && Feature.enabled(Feature.modularity) then
924+
addParamRefinements(tpt.tpe, paramss)
921925
else
922926
tpt.tpe
923927
sym.info = methodType(paramss, resType)

0 commit comments

Comments
 (0)