Skip to content

Commit 5189e68

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 4894414 commit 5189e68

38 files changed

+758
-69
lines changed

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

+7-6
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

+2
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

+6-3
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

+15-2
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

+2-7
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
@@ -182,14 +177,14 @@ trait PatternTypeConstrainer { self: TypeComparer =>
182177
case AndType(scrut1, scrut2) =>
183178
constrainPatternType(pat, scrut1) && constrainPatternType(pat, scrut2)
184179
case scrut: RefinedOrRecType =>
185-
constrainPatternType(pat, stripRefinement(scrut))
180+
constrainPatternType(pat, scrut.stripRefinement)
186181
case scrut => dealiasDropNonmoduleRefs(pat) match {
187182
case OrType(pat1, pat2) =>
188183
either(constrainPatternType(pat1, scrut), constrainPatternType(pat2, scrut))
189184
case AndType(pat1, pat2) =>
190185
constrainPatternType(pat1, scrut) && constrainPatternType(pat2, scrut)
191186
case pat: RefinedOrRecType =>
192-
constrainPatternType(stripRefinement(pat), scrut)
187+
constrainPatternType(pat.stripRefinement, scrut)
193188
case pat =>
194189
tryConstrainSimplePatternType(pat, scrut)
195190
|| classesMayBeCompatible && constrainUpcasted(scrut)

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

+1
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ object StdNames {
629629
val toString_ : N = "toString"
630630
val toTypeConstructor: N = "toTypeConstructor"
631631
val tpe : N = "tpe"
632+
val tracked: N = "tracked"
632633
val transparent : N = "transparent"
633634
val tree : N = "tree"
634635
val true_ : N = "true"

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -1187,21 +1187,25 @@ object SymDenotations {
11871187
final def isExtensibleClass(using Context): Boolean =
11881188
isClass && !isOneOf(FinalOrModuleClass) && !isAnonymousClass
11891189

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

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

12061210
final def isLocalToCompilationUnit(using Context): Boolean =
12071211
is(Private)

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

+11-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*
77
import Names.{Name, TermName}
88
import Constants.Constant
99

10-
class TypeUtils {
10+
import Names.Name
1111

12+
class TypeUtils:
1213
/** A decorator that provides methods on types
1314
* that are needed in the transformer pipeline.
1415
*/
15-
extension (self: Type) {
16+
extension (self: Type)
1617

1718
def isErasedValueType(using Context): Boolean =
1819
self.isInstanceOf[ErasedValueType]
@@ -178,5 +179,11 @@ class TypeUtils {
178179
def isThisTypeOf(cls: Symbol)(using Context) = self match
179180
case self: Types.ThisType => self.cls == cls
180181
case _ => false
181-
}
182-
}
182+
183+
/** Strip all outer refinements off this type */
184+
def stripRefinement: Type = self match
185+
case self: RefinedOrRecType => self.parent.stripRefinement
186+
case seld => self
187+
188+
end TypeUtils
189+

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

+1
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
867867
if (flags.is(Exported)) writeModTag(EXPORTED)
868868
if (flags.is(Given)) writeModTag(GIVEN)
869869
if (flags.is(Implicit)) writeModTag(IMPLICIT)
870+
if (flags.is(Tracked)) writeModTag(TRACKED)
870871
if (isTerm) {
871872
if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY)
872873
if (flags.is(AbsOverride)) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) }

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

+5-1
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.*
@@ -755,6 +756,7 @@ class TreeUnpickler(reader: TastyReader,
755756
case INVISIBLE => addFlag(Invisible)
756757
case TRANSPARENT => addFlag(Transparent)
757758
case INFIX => addFlag(Infix)
759+
case TRACKED => addFlag(Tracked)
758760
case PRIVATEqualified =>
759761
readByte()
760762
privateWithin = readWithin
@@ -922,6 +924,8 @@ class TreeUnpickler(reader: TastyReader,
922924
val resType =
923925
if name == nme.CONSTRUCTOR then
924926
effectiveResultType(sym, paramss)
927+
else if sym.isAllOf(Given | Method) && Feature.enabled(Feature.modularity) then
928+
addParamRefinements(tpt.tpe, paramss)
925929
else
926930
tpt.tpe
927931
sym.info = methodType(paramss, resType)

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+10-5
Original file line numberDiff line numberDiff line change
@@ -3189,6 +3189,7 @@ object Parsers {
31893189
case nme.open => Mod.Open()
31903190
case nme.transparent => Mod.Transparent()
31913191
case nme.infix => Mod.Infix()
3192+
case nme.tracked => Mod.Tracked()
31923193
}
31933194
}
31943195

@@ -3255,7 +3256,8 @@ object Parsers {
32553256
* | AccessModifier
32563257
* | override
32573258
* | opaque
3258-
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | inline | transparent | infix | erased
3259+
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased |
3260+
* inline | transparent | infix
32593261
*/
32603262
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
32613263
@tailrec
@@ -3408,8 +3410,8 @@ object Parsers {
34083410
/** ClsTermParamClause ::= ‘(’ ClsParams ‘)’ | UsingClsTermParamClause
34093411
* UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’
34103412
* ClsParams ::= ClsParam {‘,’ ClsParam}
3411-
* ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’)] Param
3412-
*
3413+
* ClsParam ::= {Annotation}
3414+
* [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param
34133415
* TypelessClause ::= DefTermParamClause
34143416
* | UsingParamClause
34153417
*
@@ -3445,6 +3447,8 @@ object Parsers {
34453447
if isErasedKw then
34463448
mods = addModifier(mods)
34473449
if paramOwner.isClass then
3450+
if isIdent(nme.tracked) && in.featureEnabled(Feature.modularity) && !in.lookahead.isColon then
3451+
mods = addModifier(mods)
34483452
mods = addFlag(modifiers(start = mods), ParamAccessor)
34493453
mods =
34503454
if in.token == VAL then
@@ -3516,7 +3520,8 @@ object Parsers {
35163520
val isParams =
35173521
!impliedMods.is(Given)
35183522
|| startParamTokens.contains(in.token)
3519-
|| isIdent && (in.name == nme.inline || in.lookahead.isColon)
3523+
|| isIdent
3524+
&& (in.name == nme.inline || in.name == nme.tracked || in.lookahead.isColon)
35203525
(mods, isParams)
35213526
(if isParams then commaSeparated(() => param())
35223527
else contextTypes(paramOwner, numLeadParams, impliedMods)) match {
@@ -4104,7 +4109,7 @@ object Parsers {
41044109
def adjustDefParams(paramss: List[ParamClause]): List[ParamClause] =
41054110
paramss.nestedMap: param =>
41064111
if !param.mods.isAllOf(PrivateLocal) then
4107-
syntaxError(em"method parameter ${param.name} may not be `a val`", param.span)
4112+
syntaxError(em"method parameter ${param.name} may not be a `val`", param.span)
41084113
param.withMods(param.mods &~ (AccessFlags | ParamAccessor | Mutable) | Param)
41094114
.asInstanceOf[List[ParamClause]]
41104115

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
113113
protected def refinementNameString(tp: RefinedType): String = nameString(tp.refinedName)
114114

115115
/** String representation of a refinement */
116-
protected def toTextRefinement(rt: RefinedType): Text =
116+
def toTextRefinement(rt: RefinedType): Text =
117117
val keyword = rt.refinedInfo match {
118118
case _: ExprType | _: MethodOrPoly => "def "
119119
case _: TypeBounds => "type "

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package printing
44

55
import core.*
66
import Texts.*, ast.Trees.*
7-
import Types.{Type, SingletonType, LambdaParam, NamedType},
7+
import Types.{Type, SingletonType, LambdaParam, NamedType, RefinedType},
88
Symbols.Symbol, Scopes.Scope, Constants.Constant,
99
Names.Name, Denotations._, Annotations.Annotation, Contexts.Context
1010
import typer.Implicits.*
@@ -104,6 +104,9 @@ abstract class Printer {
104104
/** Textual representation of a prefix of some reference, ending in `.` or `#` */
105105
def toTextPrefixOf(tp: NamedType): Text
106106

107+
/** textual representation of a refinement, with no enclosing {...} */
108+
def toTextRefinement(rt: RefinedType): Text
109+
107110
/** Textual representation of a reference in a capture set */
108111
def toTextCaptureRef(tp: Type): Text
109112

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

+12-4
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,15 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
369369
case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) =>
370370
// need to check instantiability here, because the type of the New itself
371371
// might be a type constructor.
372-
ctx.typer.checkClassType(tree.tpe, tree.srcPos, traitReq = false, stablePrefixReq = true)
372+
def checkClassType(tpe: Type, stablePrefixReq: Boolean) =
373+
ctx.typer.checkClassType(tpe, tree.srcPos,
374+
traitReq = false, stablePrefixReq = stablePrefixReq,
375+
refinementOK = Feature.enabled(Feature.modularity))
376+
checkClassType(tree.tpe, true)
373377
if !nu.tpe.isLambdaSub then
374378
// Check the constructor type as well; it could be an illegal singleton type
375379
// which would not be reflected as `tree.tpe`
376-
ctx.typer.checkClassType(nu.tpe, tree.srcPos, traitReq = false, stablePrefixReq = false)
380+
checkClassType(nu.tpe, false)
377381
Checking.checkInstantiable(tree.tpe, nu.tpe, nu.srcPos)
378382
withNoCheckNews(nu :: Nil)(app1)
379383
case _ =>
@@ -448,8 +452,12 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
448452
// Constructor parameters are in scope when typing a parent.
449453
// While they can safely appear in a parent tree, to preserve
450454
// soundness we need to ensure they don't appear in a parent
451-
// type (#16270).
452-
val illegalRefs = parent.tpe.namedPartsWith(p => p.symbol.is(ParamAccessor) && (p.symbol.owner eq sym))
455+
// type (#16270). We can strip any refinement of a parent type since
456+
// these refinements are split off from the parent type constructor
457+
// application `parent` in Namer and don't show up as parent types
458+
// of the class.
459+
val illegalRefs = parent.tpe.dealias.stripRefinement.namedPartsWith:
460+
p => p.symbol.is(ParamAccessor) && (p.symbol.owner eq sym)
453461
if illegalRefs.nonEmpty then
454462
report.error(
455463
em"The type of a class parent cannot refer to constructor parameters, but ${parent.tpe} refers to ${illegalRefs.map(_.name.show).mkString(",")}", parent.srcPos)

0 commit comments

Comments
 (0)