From 646529176187c673927307adbfa96a2ceed184a2 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 10 Oct 2024 17:11:55 +0200 Subject: [PATCH 1/9] First draft of abstract tracked vals --- .../src/dotty/tools/dotc/ast/Desugar.scala | 5 ++-- .../src/dotty/tools/dotc/parsing/Tokens.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 28 +++++++++++++------ tests/pos/abstract-tracked.scala | 19 +++++++++++++ 4 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 tests/pos/abstract-tracked.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index e1a6b97fc7d3..f5f09cbe646c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index b0a533b2f1df..78118cc86726 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -297,7 +297,7 @@ object Tokens extends TokensCommon { final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE) - final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix) + final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix, nme.tracked) def showTokenDetailed(token: Int): String = debugString(token) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ce72aac596c0..f9a3c4400c3f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1426,7 +1426,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer index(stats) typedStats(stats, ctx.owner) - def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = { + def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = trace.force(i"typing block $tree, pt = $pt") { val (stats1, exprCtx) = withoutMode(Mode.Pattern) { typedBlockStats(tree.stats) } @@ -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 = trace.force(s"typedTypeTree $tree: $pt") { tree match case tree: untpd.DerivedTypeTree => tree.ensureCompletions @@ -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 = @@ -2820,7 +2821,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => typed(rhs) - def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = { + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = trace.force(i"typed val def $vdef, sym = $sym") { val ValDef(name, tpt, _) = vdef checkNonRootName(vdef.name, vdef.nameSpan) completeAnnotations(vdef, sym) @@ -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 tpt.isEmpty 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.allOverriddenSymbols.exists(_.flags.is(Tracked)) then + sym.setFlag(Tracked) + if tpt.isEmpty then + sym.info = rhs.tpe + + 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 = @@ -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.force(i"typing $tree", typr) { trace(i"typing $tree, pt = $pt", typr, show = true) { record(s"typed $getClass") record("typed total") @@ -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) @@ -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 = trace.force(i"typing type $tree, pt = $pt") { val tree1 = withMode(Mode.Type) { typed(tree, pt) } if mapPatternBounds && ctx.mode.is(Mode.Pattern) && !ctx.isAfterTyper then tree1 match @@ -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)) diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala new file mode 100644 index 000000000000..ac8cfed2d3b2 --- /dev/null +++ b/tests/pos/abstract-tracked.scala @@ -0,0 +1,19 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait F: + tracked val x: Int + +trait G: + tracked val y: Int + +object Test: + // val f : F(1) /*: F { val x: 1 }*/ = new F: + // val x: 1 = 1 + val f = new F: + val x = 1 + val g = new G: + val y: 2 = 2 + + summon[f.x.type <:< 1] + summon[g.y.type <:< 2] From f1d146d6c7f3acfcdd8e01bebaa000ace514cd84 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 11 Oct 2024 17:27:52 +0200 Subject: [PATCH 2/9] Disallow tracked for unusupported definitions, move tracked modifier behind a flag, reemove debugs and add some more tests --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 5 +++++ compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 3 ++- compiler/src/dotty/tools/dotc/parsing/Tokens.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 11 +++++------ tests/neg/abstract-tracked.check | 5 +++++ tests/neg/abstract-tracked.scala | 5 +++++ tests/pos/abstract-tracked.scala | 6 ++++++ 7 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 tests/neg/abstract-tracked.check create mode 100644 tests/neg/abstract-tracked.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index f5f09cbe646c..dac22068ff2e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1481,6 +1481,10 @@ object desugar { rhsOK(rhs) } + val legalTracked: MemberDefTest = { + case ValDef(_, _, _) => true + } + def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef = def check(rhs: Tree): MemberDef = rhs match case bounds: TypeBoundsTree if bounds.alias.isEmpty => @@ -1506,6 +1510,7 @@ object desugar { } else tested tested = checkOpaqueAlias(tested) tested = checkApplicable(Opaque, legalOpaque) + tested = checkApplicable(Tracked, legalTracked) tested case _ => tree diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 2dc0a1a8d805..2007b633a7c5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -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 @@ -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() diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 78118cc86726..b0a533b2f1df 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -297,7 +297,7 @@ object Tokens extends TokensCommon { final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE) - final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix, nme.tracked) + final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix) def showTokenDetailed(token: Int): String = debugString(token) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f9a3c4400c3f..159c312d1225 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1426,7 +1426,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer index(stats) typedStats(stats, ctx.owner) - def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = trace.force(i"typing block $tree, pt = $pt") { + def typedBlock(tree: untpd.Block, pt: Type)(using Context): Tree = { val (stats1, exprCtx) = withoutMode(Mode.Pattern) { typedBlockStats(tree.stats) } @@ -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 = trace.force(s"typedTypeTree $tree: $pt") { + def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = { tree match case tree: untpd.DerivedTypeTree => tree.ensureCompletions @@ -2821,7 +2821,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => typed(rhs) - def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = trace.force(i"typed val def $vdef, sym = $sym") { + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = { val ValDef(name, tpt, _) = vdef checkNonRootName(vdef.name, vdef.nameSpan) completeAnnotations(vdef, sym) @@ -2847,7 +2847,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if tpt.isEmpty then sym.info = rhs.tpe - 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 = @@ -3638,7 +3637,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 = trace.force(i"typing $tree", typr) { + 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") @@ -3766,7 +3765,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 = trace.force(i"typing type $tree, pt = $pt") { + 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 diff --git a/tests/neg/abstract-tracked.check b/tests/neg/abstract-tracked.check new file mode 100644 index 000000000000..5e0e641fff69 --- /dev/null +++ b/tests/neg/abstract-tracked.check @@ -0,0 +1,5 @@ +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:4:14 ---------------------------------------------------------- +4 |tracked trait F: // error + |^ + |Modifier tracked is not allowed for this definition +5 | val x: Int diff --git a/tests/neg/abstract-tracked.scala b/tests/neg/abstract-tracked.scala new file mode 100644 index 000000000000..ad52243afd1a --- /dev/null +++ b/tests/neg/abstract-tracked.scala @@ -0,0 +1,5 @@ +import scala.language.experimental.modularity +import scala.language.future + +tracked trait F: // error + val x: Int diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala index ac8cfed2d3b2..cc6f824ed38f 100644 --- a/tests/pos/abstract-tracked.scala +++ b/tests/pos/abstract-tracked.scala @@ -7,6 +7,9 @@ trait F: trait G: tracked val y: Int +trait H: + tracked val z: Int = 3 + object Test: // val f : F(1) /*: F { val x: 1 }*/ = new F: // val x: 1 = 1 @@ -14,6 +17,9 @@ object Test: val x = 1 val g = new G: val y: 2 = 2 + val h = new H: + override val z = 4 summon[f.x.type <:< 1] summon[g.y.type <:< 2] + summon[h.z.type <:< 4] From d2b6a6186118309a9a2c1a2ad3336b5785828e07 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 14 Oct 2024 10:33:52 +0200 Subject: [PATCH 3/9] Fix handling tracked param accessors --- .../src/dotty/tools/dotc/ast/Desugar.scala | 6 ++++-- .../src/dotty/tools/dotc/typer/Namer.scala | 8 +++---- .../src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- tests/neg/abstract-tracked.check | 21 ++++++++++++++++--- tests/neg/abstract-tracked.scala | 13 ++++++++++-- tests/pos/abstract-tracked.scala | 15 +++++++++++++ 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index dac22068ff2e..43d21731887a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1481,8 +1481,10 @@ object desugar { rhsOK(rhs) } - val legalTracked: MemberDefTest = { - case ValDef(_, _, _) => true + val legalTracked: Context ?=> MemberDefTest = { + case valdef @ ValDef(_, _, _) => + val sym = valdef.symbol + ctx.owner.exists && (ctx.owner.isClass || ctx.owner.isConstructor) } def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6167db62fbe0..0849e57b8c7d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 159c312d1225..74735a8b50c1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2835,14 +2835,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer excludeDeferredGiven(rhs, sym): typedExpr(_, tpt1.tpe.widenExpr) setAbstractTrackedInfo(sym, rhs1, tpt) - val tpt2 = if tpt.isEmpty then TypeTree(rhs1.tpe) else tpt1 + val tpt2 = if sym.flags.is(Tracked) && tpt.isEmpty && !sym.flags.is(ParamAccessor) 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.allOverriddenSymbols.exists(_.flags.is(Tracked)) then + if sym.allOverriddenSymbols.exists(_.flags.is(Tracked)) && !sym.flags.is(ParamAccessor) then sym.setFlag(Tracked) if tpt.isEmpty then sym.info = rhs.tpe diff --git a/tests/neg/abstract-tracked.check b/tests/neg/abstract-tracked.check index 5e0e641fff69..70a85e81df85 100644 --- a/tests/neg/abstract-tracked.check +++ b/tests/neg/abstract-tracked.check @@ -1,5 +1,20 @@ -- [E156] Syntax Error: tests/neg/abstract-tracked.scala:4:14 ---------------------------------------------------------- -4 |tracked trait F: // error - |^ +4 |tracked trait F // error + |^^^^^^^^^^^^^^^ |Modifier tracked is not allowed for this definition -5 | val x: Int +-- [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 diff --git a/tests/neg/abstract-tracked.scala b/tests/neg/abstract-tracked.scala index ad52243afd1a..ff4a7ea8174f 100644 --- a/tests/neg/abstract-tracked.scala +++ b/tests/neg/abstract-tracked.scala @@ -1,5 +1,14 @@ import scala.language.experimental.modularity import scala.language.future -tracked trait F: // error - val x: Int +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 diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala index cc6f824ed38f..fa6960894b75 100644 --- a/tests/pos/abstract-tracked.scala +++ b/tests/pos/abstract-tracked.scala @@ -10,6 +10,13 @@ trait G: trait H: tracked val z: Int = 3 +trait I extends F + +trait J extends F: + val x: Int = 1 + +class K(tracked val x: Int) + object Test: // val f : F(1) /*: F { val x: 1 }*/ = new F: // val x: 1 = 1 @@ -19,7 +26,15 @@ object Test: val y: 2 = 2 val h = new H: override val z = 4 + val i = new I: + val x = 5 + val j = new J: + override val x = 6 + val k = new K(7) summon[f.x.type <:< 1] summon[g.y.type <:< 2] summon[h.z.type <:< 4] + summon[i.x.type <:< 5] + summon[j.x.type <:< 6] + summon[k.x.type <:< 7] From f7b7188e2d86c75d086fd4578144c7e1bb17c8d4 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 14 Oct 2024 15:31:30 +0200 Subject: [PATCH 4/9] Fix tracked modifier checks --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- tests/neg/tracked.check | 50 ++++++------------- tests/neg/tracked.scala | 6 +-- 4 files changed, 22 insertions(+), 40 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 43d21731887a..f920db0933ce 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1484,7 +1484,7 @@ object desugar { val legalTracked: Context ?=> MemberDefTest = { case valdef @ ValDef(_, _, _) => val sym = valdef.symbol - ctx.owner.exists && (ctx.owner.isClass || ctx.owner.isConstructor) + !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 = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 74735a8b50c1..238c983eb798 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2835,14 +2835,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer excludeDeferredGiven(rhs, sym): typedExpr(_, tpt1.tpe.widenExpr) setAbstractTrackedInfo(sym, rhs1, tpt) - val tpt2 = if sym.flags.is(Tracked) && tpt.isEmpty && !sym.flags.is(ParamAccessor) then TypeTree(rhs1.tpe) else tpt1 + 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.allOverriddenSymbols.exists(_.flags.is(Tracked)) && !sym.flags.is(ParamAccessor) then + if sym.allOverriddenSymbols.exists(_.flags.is(Tracked)) && !sym.flags.is(ParamAccessor) && !sym.flags.is(Param) then sym.setFlag(Tracked) if tpt.isEmpty then sym.info = rhs.tpe diff --git a/tests/neg/tracked.check b/tests/neg/tracked.check index 14a4d2a08300..3494c401a007 100644 --- a/tests/neg/tracked.check +++ b/tests/neg/tracked.check @@ -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 | ^^^^^^^^^^^^^^^^^^ @@ -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 diff --git a/tests/neg/tracked.scala b/tests/neg/tracked.scala index 9f874ca3c0da..3d6c1a14fc55 100644 --- a/tests/neg/tracked.scala +++ b/tests/neg/tracked.scala @@ -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 From 567b2c1f832bcc1e105c3a078444f67d200ff0e2 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Wed, 16 Oct 2024 10:10:13 +0200 Subject: [PATCH 5/9] Support inferring precise types for non-overriding members declared as tracked --- .../src/dotty/tools/dotc/typer/Typer.scala | 7 +-- tests/pos/abstract-tracked.scala | 48 +++++++++++-------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 238c983eb798..ca1a2a98c55d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2842,9 +2842,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } private def setAbstractTrackedInfo(sym: Symbol, rhs: Tree, tpt: untpd.Tree)(using Context): Unit = - if sym.allOverriddenSymbols.exists(_.flags.is(Tracked)) && !sym.flags.is(ParamAccessor) && !sym.flags.is(Param) then - sym.setFlag(Tracked) - if tpt.isEmpty then + 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 private def retractDefDef(sym: Symbol)(using Context): Tree = diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala index fa6960894b75..e0743d4b0347 100644 --- a/tests/pos/abstract-tracked.scala +++ b/tests/pos/abstract-tracked.scala @@ -2,39 +2,49 @@ import scala.language.experimental.modularity import scala.language.future trait F: - tracked val x: Int + tracked val a: Int trait G: - tracked val y: Int + tracked val b: Int trait H: - tracked val z: Int = 3 + tracked val c: Int = 3 trait I extends F trait J extends F: - val x: Int = 1 + val a: Int = 1 -class K(tracked val x: Int) +class K(tracked val d: Int) + +class L + +trait M: + val f: Int object Test: - // val f : F(1) /*: F { val x: 1 }*/ = new F: - // val x: 1 = 1 val f = new F: - val x = 1 + val a = 1 val g = new G: - val y: 2 = 2 + val b: 2 = 2 val h = new H: - override val z = 4 + override val c = 4 val i = new I: - val x = 5 + val a = 5 val j = new J: - override val x = 6 + override val a = 6 val k = new K(7) - - summon[f.x.type <:< 1] - summon[g.y.type <:< 2] - summon[h.z.type <:< 4] - summon[i.x.type <:< 5] - summon[j.x.type <:< 6] - summon[k.x.type <:< 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] From 28b5725d95081dc5145916d26187954bb7dc4657 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 1 Nov 2024 14:49:31 +0100 Subject: [PATCH 6/9] Move setAbstractTrackedInfo to Namer.Completer --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 11 +++++++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 16 +++------------- tests/pos/abstract-tracked-2.scala | 11 +++++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 tests/pos/abstract-tracked-2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0849e57b8c7d..680af14ba9b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -907,6 +907,16 @@ class Namer { typer: Typer => case _ => } + private def setAbstractTrackedInfo(sym: Symbol)(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) then + original match + case tree: untpd.ValDef if tree.tpt.isEmpty => + sym.info = typedAheadExpr(tree.rhs).tpe + case _ => () + /** Invalidate `denot` by overwriting its info with `NoType` if * `denot` is a compiler generated case class method that clashes * with a user-defined method in the same scope with a matching type. @@ -989,6 +999,7 @@ class Namer { typer: Typer => addInlineInfo(sym) denot.info = typeSig(sym) invalidateIfClashingSynthetic(denot) + setAbstractTrackedInfo(sym) Checking.checkWellFormed(sym) denot.info = avoidPrivateLeaks(sym) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ca1a2a98c55d..7ef6040daf90 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2834,20 +2834,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case rhs => excludeDeferredGiven(rhs, sym): typedExpr(_, tpt1.tpe.widenExpr) - 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 + val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + postProcessInfo(vdef1, sym) + vdef1.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 - 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 = diff --git a/tests/pos/abstract-tracked-2.scala b/tests/pos/abstract-tracked-2.scala new file mode 100644 index 000000000000..01e4ee84c548 --- /dev/null +++ b/tests/pos/abstract-tracked-2.scala @@ -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 From 4133b8388b1151431a026d0181dc800d61bd7545 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sun, 3 Nov 2024 15:59:11 +0100 Subject: [PATCH 7/9] Move tracked inference logic to `inferredResultType ` --- .../src/dotty/tools/dotc/core/Flags.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 26 +++++++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index b915373da021..0775b3caaf0c 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 680af14ba9b3..f356057b680c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -907,16 +907,6 @@ class Namer { typer: Typer => case _ => } - private def setAbstractTrackedInfo(sym: Symbol)(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) then - original match - case tree: untpd.ValDef if tree.tpt.isEmpty => - sym.info = typedAheadExpr(tree.rhs).tpe - case _ => () - /** Invalidate `denot` by overwriting its info with `NoType` if * `denot` is a compiler generated case class method that clashes * with a user-defined method in the same scope with a matching type. @@ -999,7 +989,6 @@ class Namer { typer: Typer => addInlineInfo(sym) denot.info = typeSig(sym) invalidateIfClashingSynthetic(denot) - setAbstractTrackedInfo(sym) Checking.checkWellFormed(sym) denot.info = avoidPrivateLeaks(sym) } @@ -2062,12 +2051,17 @@ 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 + 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") - tp & iResType + if iSym.is(Tracked) && !mdef.rhs.isEmpty then + // When inherting a tracked member, we infer a precise type from the rhs + tp & typedAheadExpr(mdef.rhs, iResType).tpe + else + if (iResType.exists) + typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") + tp & iResType } end inherited From 9ff1af77c96d0fb9070f934d33386d9ba957a4e5 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sun, 3 Nov 2024 16:10:30 +0100 Subject: [PATCH 8/9] Also infer a precise type when tracked is not inherited --- .../src/dotty/tools/dotc/typer/Namer.scala | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index f356057b680c..9ff01fb1a20e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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. @@ -2053,15 +2058,12 @@ class Namer { typer: Typer => 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 iSym.is(Tracked) && !mdef.rhs.isEmpty then - // When inherting a tracked member, we infer a precise type from the rhs - tp & typedAheadExpr(mdef.rhs, iResType).tpe - else - if (iResType.exists) - typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") - tp & iResType + if (iResType.exists) + typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") + tp & iResType } end inherited @@ -2146,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 @@ -2156,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 { From 8208d4642aa4b410dae0eb77212fc230af4fe1b8 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 18 Nov 2024 11:20:50 +0100 Subject: [PATCH 9/9] Add a section about tracked members to the modularity doc and update the syntax reference --- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- docs/_docs/internals/syntax.md | 5 ++- .../reference/experimental/modularity.md | 40 +++++++++++++++---- tests/neg/abstract-tracked-1.scala | 12 ++++++ tests/pos/abstract-tracked.scala | 5 +++ 5 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 tests/neg/abstract-tracked-1.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5a3be6505715..929a6d8d46d1 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3489,7 +3489,7 @@ object Parsers { * UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’ * ClsParams ::= ClsParam {‘,’ ClsParam} * ClsParam ::= {Annotation} - * [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param + * [{Modifier} (‘val’ | ‘var’)] Param * TypelessClause ::= DefTermParamClause * | UsingParamClause * @@ -3527,8 +3527,6 @@ object Parsers { if isErasedKw then mods = addModifier(mods) if paramOwner.isClass then - if isIdent(nme.tracked) && in.featureEnabled(Feature.modularity) && !in.lookahead.isColon then - mods = addModifier(mods) mods = addFlag(modifiers(start = mods), ParamAccessor) mods = if in.token == VAL then diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index d0074bb503c2..665b4f5144ba 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -141,7 +141,7 @@ type val var while with yield ### Soft keywords ``` -as derives end erased extension infix inline opaque open throws transparent using | * + - +as derives end erased extension infix inline opaque open throws tracked transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -381,7 +381,7 @@ ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var - [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param + [{Modifier} (‘val’ | ‘var’)] Param DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent DefParamClause ::= DefTypeParamClause @@ -418,6 +418,7 @@ LocalModifier ::= ‘abstract’ | ‘transparent’ | ‘infix’ | ‘erased’ + | ‘tracked’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index a989b71770af..66d4c0c23ede 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -108,14 +108,6 @@ This works as it should now. Without the addition of `tracked` to the parameter of `SetFunctor` typechecking would immediately lose track of the element type `T` after an `add`, and would therefore fail. -**Syntax Change** - -``` -ClsParam ::= {Annotation} [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param -``` - -The (soft) `tracked` modifier is only allowed for `val` parameters of classes. - **Discussion** Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break: @@ -134,6 +126,38 @@ only if the class refers to a type member of `x`. But it turns out that this scheme is unimplementable since it would quickly lead to cyclic references when typechecking recursive class graphs. So an explicit `tracked` looks like the best available option. +## Tracked members + +The `tracked` modifier can also be used for `val` members of classes and traits +to force the type of the member (or it's overriding member) to be as exact as +possible. More precisely, it will will assign the `tracked` member the infered +type of the rhs. For instance, consider the following definition: + +```scala +trait F: + tracked val a: Int + tracked val b: Int + +class N extends F: + val a = 22 // a.type =:= 22 + val b: Int = 22 // b.type =:= Int + tracked val c = 22 // c.type =:= 22 +``` + +Here, the `tracked` modifier ensures that the type of `a` in `N` is `22` and not +`Int`. But the type of `b` is `N` is `Int` since it's explicitly declared as +`Int`. `tracked` members can also be immediately initialized, as in the case of +`c`. + +## Tracked syntax change + +``` +LocalModifier ::= ‘tracked’ +``` + +The (soft) `tracked` modifier is allowed as a local modifier. + + ## Allow Class Parents to be Refined Types Since `tracked` parameters create refinements in constructor types, diff --git a/tests/neg/abstract-tracked-1.scala b/tests/neg/abstract-tracked-1.scala new file mode 100644 index 000000000000..0aef9f938816 --- /dev/null +++ b/tests/neg/abstract-tracked-1.scala @@ -0,0 +1,12 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait F: + tracked val a: Int + +class G: + val a: Int = 1 + +def Test = + val g = new G + summon[g.a.type <:< 1] // error diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala index e0743d4b0347..21812db9c04d 100644 --- a/tests/pos/abstract-tracked.scala +++ b/tests/pos/abstract-tracked.scala @@ -22,6 +22,9 @@ class L trait M: val f: Int +class N extends F: + val a = 10 + object Test: val f = new F: val a = 1 @@ -39,6 +42,7 @@ object Test: } val m = new M: tracked val f = 9 + val n = new N summon[f.a.type <:< 1] summon[g.b.type <:< 2] @@ -48,3 +52,4 @@ object Test: 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] + summon[n.a.type <:< 10]