Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tracked members #21761

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
12 changes: 10 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1007,12 +1007,13 @@ object desugar {
if mods.isAllOf(Given | Inline | Transparent) then
report.error("inline given instances cannot be trasparent", cdef)
var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
if vparamAccessors.exists(_.mods.is(Tracked)) then
val newBody = tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths
if newBody.collect { case d: ValOrDefDef => d }.exists(_.mods.is(Tracked)) then
classMods |= Dependent
cpy.TypeDef(cdef: TypeDef)(
name = className,
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,
tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths)
newBody)
).withMods(classMods)
}

Expand Down Expand Up @@ -1480,6 +1481,12 @@ object desugar {
rhsOK(rhs)
}

val legalTracked: Context ?=> MemberDefTest = {
case valdef @ ValDef(_, _, _) =>
val sym = valdef.symbol
!ctx.owner.exists || ctx.owner.isClass || ctx.owner.is(Case) || ctx.owner.isConstructor || valdef.mods.is(Param) || valdef.mods.is(ParamAccessor)
}

def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef =
def check(rhs: Tree): MemberDef = rhs match
case bounds: TypeBoundsTree if bounds.alias.isEmpty =>
Expand All @@ -1505,6 +1512,7 @@ object desugar {
} else tested
tested = checkOpaqueAlias(tested)
tested = checkApplicable(Opaque, legalOpaque)
tested = checkApplicable(Tracked, legalTracked)
tested
case _ =>
tree
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ object Flags {
*/
val AfterLoadFlags: FlagSet = commonFlags(
FromStartFlags, AccessFlags, Final, AccessorOrSealed,
Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent, Tracked)
Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent)

/** A value that's unstable unless complemented with a Stable flag */
val UnstableValueFlags: FlagSet = Mutable | Method
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ object Scanners {

def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext)
def erasedEnabled = featureEnabled(Feature.erasedDefinitions)
def trackedEnabled = featureEnabled(Feature.modularity)

private var postfixOpsEnabledCache = false
private var postfixOpsEnabledCtx: Context = NoContext
Expand Down Expand Up @@ -1195,7 +1196,7 @@ object Scanners {

def isSoftModifier: Boolean =
token == IDENTIFIER
&& (softModifierNames.contains(name) || name == nme.erased && erasedEnabled)
&& (softModifierNames.contains(name) || name == nme.erased && erasedEnabled || name == nme.tracked && trackedEnabled)

def isSoftModifierInModifierPosition: Boolean =
isSoftModifier && inModifierPosition()
Expand Down
22 changes: 15 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -877,16 +877,16 @@ class Namer { typer: Typer =>
protected def addAnnotations(sym: Symbol): Unit = original match {
case original: untpd.MemberDef =>
lazy val annotCtx = annotContext(original, sym)
original.setMods:
original.setMods:
original.mods.withAnnotations :
original.mods.annotations.mapConserve: annotTree =>
original.mods.annotations.mapConserve: annotTree =>
val cls = typedAheadAnnotationClass(annotTree)(using annotCtx)
if (cls eq sym)
report.error(em"An annotation class cannot be annotated with iself", annotTree.srcPos)
annotTree
else
val ann =
if cls.is(JavaDefined) then Checking.checkNamedArgumentForJavaAnnotation(annotTree, cls.asClass)
val ann =
if cls.is(JavaDefined) then Checking.checkNamedArgumentForJavaAnnotation(annotTree, cls.asClass)
else annotTree
val ann1 = Annotation.deferred(cls)(typedAheadExpr(ann)(using annotCtx))
sym.addAnnotation(ann1)
Expand Down Expand Up @@ -2016,6 +2016,11 @@ class Namer { typer: Typer =>
paramFn: Type => Type,
fallbackProto: Type
)(using Context): Type =
/** Is this member tracked? This is true if it is marked as `tracked` or if
* it overrides a `tracked` member. To account for the later, `isTracked`
* is overriden to `true` as a side-effect of computing `inherited`.
*/
var isTracked: Boolean = sym.is(Tracked)

/** A type for this definition that might be inherited from elsewhere:
* If this is a setter parameter, the corresponding getter type.
Expand Down Expand Up @@ -2051,8 +2056,10 @@ class Namer { typer: Typer =>
if paramss.isEmpty then info.widenExpr
else NoType

val iRawInfo =
cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName).info
val iDenot = cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName)
val iSym = iDenot.symbol
if iSym.is(Tracked) then isTracked = true
val iRawInfo = iDenot.info
val iResType = instantiatedResType(iRawInfo, paramss).asSeenFrom(site, cls)
if (iResType.exists)
typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType")
Expand Down Expand Up @@ -2141,6 +2148,7 @@ class Namer { typer: Typer =>
if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null)
match
case ctp: ConstantType if sym.isInlineVal => ctp
case tp if isTracked => tp
case tp => TypeComparer.widenInferred(tp, pt, Widen.Unions)

// Replace aliases to Unit by Unit itself. If we leave the alias in
Expand All @@ -2151,7 +2159,7 @@ class Namer { typer: Typer =>
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos)
//if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType")
if (inherited.exists)
if sym.isInlineVal then lhsType else inherited
if sym.isInlineVal || isTracked then lhsType else inherited
else {
if (sym.is(Implicit))
mdef match {
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2411,7 +2411,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
else if ctx.reporter.errorsReported then UnspecifiedErrorType
else errorType(em"cannot infer type; expected type $pt is not fully defined", tree.srcPos))

def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree =
def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = {
tree match
case tree: untpd.DerivedTypeTree =>
tree.ensureCompletions
Expand All @@ -2427,6 +2427,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
}
case _ =>
completeTypeTree(InferredTypeTree(), pt, tree)
}

def typedInLambdaTypeTree(tree: untpd.InLambdaTypeTree, pt: Type)(using Context): Tree =
val tp =
Expand Down Expand Up @@ -2837,7 +2838,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
postProcessInfo(vdef1, sym)
vdef1.setDefTree
}

private def retractDefDef(sym: Symbol)(using Context): Tree =
// it's a discarded method (synthetic case class method or synthetic java record constructor or overridden member), drop it
val canBeInvalidated: Boolean =
Expand Down Expand Up @@ -3628,7 +3628,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
}

/** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */
def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree =
def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = {
trace(i"typing $tree, pt = $pt", typr, show = true) {
record(s"typed $getClass")
record("typed total")
Expand All @@ -3640,6 +3640,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
tree.withType(WildcardType)
else adapt(typedUnadapted(tree, pt, locked), pt, locked)
}
}

def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree =
typed(tree, pt, ctx.typerState.ownedVars)
Expand Down Expand Up @@ -3755,7 +3756,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree =
withoutMode(Mode.PatternOrTypeBits)(typed(tree, pt))

def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree =
def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree = {
val tree1 = withMode(Mode.Type) { typed(tree, pt) }
if mapPatternBounds && ctx.mode.is(Mode.Pattern) && !ctx.isAfterTyper then
tree1 match
Expand All @@ -3771,6 +3772,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case tree1 =>
tree1
else tree1
}

def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(using Context): Tree =
withMode(Mode.Pattern)(typed(tree, selType))
Expand Down
20 changes: 20 additions & 0 deletions tests/neg/abstract-tracked.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:4:14 ----------------------------------------------------------
4 |tracked trait F // error
|^^^^^^^^^^^^^^^
|Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:9:15 ----------------------------------------------------------
9 |tracked object O // error
|^^^^^^^^^^^^^^^^
|Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:11:14 ---------------------------------------------------------
11 |tracked class C // error
|^^^^^^^^^^^^^^^
|Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:7:14 ----------------------------------------------------------
7 | tracked def f: F // error
| ^^^^^^^^^^^^^^^^
| Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:14:14 ---------------------------------------------------------
14 | tracked val x = 1 // error
| ^^^^^^^^^^^^^^^^^
| Modifier tracked is not allowed for this definition
14 changes: 14 additions & 0 deletions tests/neg/abstract-tracked.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import scala.language.experimental.modularity
import scala.language.future

tracked trait F // error

trait G:
tracked def f: F // error

tracked object O // error

tracked class C // error

def f =
tracked val x = 1 // error
50 changes: 16 additions & 34 deletions tests/neg/tracked.check
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,6 @@
7 | def foo(tracked a: Int) = // error
| ^
| ':' expected, but identifier found
-- Error: tests/neg/tracked.scala:8:12 ---------------------------------------------------------------------------------
8 | tracked val b: Int = 2 // error
| ^^^
| end of statement expected but 'val' found
-- Error: tests/neg/tracked.scala:11:10 --------------------------------------------------------------------------------
11 | tracked object Foo // error // error
| ^^^^^^
| end of statement expected but 'object' found
-- Error: tests/neg/tracked.scala:14:10 --------------------------------------------------------------------------------
14 | tracked class D // error // error
| ^^^^^
| end of statement expected but 'class' found
-- Error: tests/neg/tracked.scala:17:10 --------------------------------------------------------------------------------
17 | tracked type T = Int // error // error
| ^^^^
| end of statement expected but 'type' found
-- Error: tests/neg/tracked.scala:20:25 --------------------------------------------------------------------------------
20 | given g2: (tracked val x: Int) => C = C(x) // error
| ^^^^^^^^^^^^^^^^^^
Expand All @@ -30,21 +14,19 @@
4 |class C2(tracked var x: Int) // error
| ^
| mutable variables may not be `tracked`
-- [E006] Not Found Error: tests/neg/tracked.scala:11:2 ----------------------------------------------------------------
11 | tracked object Foo // error // error
| ^^^^^^^
| Not found: tracked
|
| longer explanation available when compiling with `-explain`
-- [E006] Not Found Error: tests/neg/tracked.scala:14:2 ----------------------------------------------------------------
14 | tracked class D // error // error
| ^^^^^^^
| Not found: tracked
|
| longer explanation available when compiling with `-explain`
-- [E006] Not Found Error: tests/neg/tracked.scala:17:2 ----------------------------------------------------------------
17 | tracked type T = Int // error // error
| ^^^^^^^
| Not found: tracked
|
| longer explanation available when compiling with `-explain`
-- [E156] Syntax Error: tests/neg/tracked.scala:8:16 -------------------------------------------------------------------
8 | tracked val b: Int = 2 // error
| ^^^^^^^^^^^^^^^^^^^^^^
| Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/tracked.scala:11:17 ------------------------------------------------------------------
11 | tracked object Foo // error
| ^^^^^^^^^^^^^^^^^^
| Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/tracked.scala:14:16 ------------------------------------------------------------------
14 | tracked class D // error
| ^^^^^^^^^^^^^^^
| Modifier tracked is not allowed for this definition
-- [E156] Syntax Error: tests/neg/tracked.scala:17:15 ------------------------------------------------------------------
17 | tracked type T = Int // error
| ^^^^^^^^^^^^^^^^^^^^
| Modifier tracked is not allowed for this definition
6 changes: 3 additions & 3 deletions tests/neg/tracked.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ object A:
tracked val b: Int = 2 // error

object B:
tracked object Foo // error // error
tracked object Foo // error

object C:
tracked class D // error // error
tracked class D // error

object D:
tracked type T = Int // error // error
tracked type T = Int // error

object E:
given g2: (tracked val x: Int) => C = C(x) // error
11 changes: 11 additions & 0 deletions tests/pos/abstract-tracked-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import scala.language.experimental.modularity
import scala.language.future

abstract class Vec:
tracked val size: Int

@main def main =
val v = new Vec:
val size0: size.type = 10
val size = 10
val size1: size.type = 10
50 changes: 50 additions & 0 deletions tests/pos/abstract-tracked.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import scala.language.experimental.modularity
import scala.language.future

trait F:
tracked val a: Int

trait G:
tracked val b: Int

trait H:
tracked val c: Int = 3

trait I extends F

trait J extends F:
val a: Int = 1

class K(tracked val d: Int)

class L

trait M:
val f: Int

object Test:
val f = new F:
val a = 1
val g = new G:
val b: 2 = 2
val h = new H:
override val c = 4
val i = new I:
val a = 5
val j = new J:
override val a = 6
val k = new K(7)
val l = new L {
tracked val e = 8
}
val m = new M:
tracked val f = 9

summon[f.a.type <:< 1]
summon[g.b.type <:< 2]
summon[h.c.type <:< 4]
summon[i.a.type <:< 5]
summon[j.a.type <:< 6]
summon[k.d.type <:< 7]
// summon[l.e.type <:< 8] // unrelated issue -- error: e is not a member of L
summon[m.f.type <:< 9]
Loading