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 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
8 changes: 4 additions & 4 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
24 changes: 18 additions & 6 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 @@ -2833,11 +2834,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case rhs =>
excludeDeferredGiven(rhs, sym):
typedExpr(_, tpt1.tpe.widenExpr)
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
postProcessInfo(vdef1, sym)
vdef1.setDefTree
setAbstractTrackedInfo(sym, rhs1, tpt)
val tpt2 = if sym.flags.is(Tracked) && tpt.isEmpty && !sym.flags.is(ParamAccessor) && !sym.flags.is(Param) then TypeTree(rhs1.tpe) else tpt1
val vdef2 = assignType(cpy.ValDef(vdef)(name, tpt2, rhs1), sym)
postProcessInfo(vdef2, sym)
vdef2.setDefTree
}

private def setAbstractTrackedInfo(sym: Symbol, rhs: Tree, tpt: untpd.Tree)(using Context): Unit =
if !sym.flags.is(ParamAccessor) && !sym.flags.is(Param) then
if sym.allOverriddenSymbols.exists(_.flags.is(Tracked)) then
sym.setFlag(Tracked)
if sym.flags.is(Tracked) && tpt.isEmpty then
sym.info = rhs.tpe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems dangerous to set info here. This means that the symbol info gets modified when the val is type-checked, so it's not the same before and after in definition order.

Here is an example:

abstract class Vec:
  tracked val size: Int

@main def main =
  val v = new Vec:
    val size0: size.type = 10 // error: found (10 : Int), required (size : Int)
    val size = 10
    val size1: size.type = 10 // okay, because size is known to be (10 : Int) here

I am wondering if the logic wouldn't be better placed in Namer.Completer, maybe setting the flag in completeInCreationContext and computing the precise member type in valOrDefDefSig? Have you thought about doing so? However this is not easy to do without causing cyclic references.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't been able to do exactly what I described above due to cyclic references, but I could move the whole setAbstractTrackedInfo to Namer.Completer: dotty-staging#53.
Still seems dirty to write over .info, but at least it's done directly when completing the symbol. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, seemed dirty to me too, but it also seemed like the least intrusive way to do it. If moving the logic to Namer works, then it should be the better solution, so we can merge dotty-staging#53.
I would probably wait on Martin's opinion though. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me to wait on Martin's opinion.
And my PR currently fails picklings test actually.


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 +3638,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 +3650,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 +3766,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 +3782,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
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