Skip to content
Closed
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
34 changes: 32 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Constructors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Decorators._
import DenotTransformers._
import Constants.Constant
import collection.mutable
import config.Feature

object Constructors {
val name: String = "constructors"
Expand Down Expand Up @@ -244,6 +245,36 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
}
splitStats(tree.body)

// Check that the pair of parameter `param` and parameter accessor `acc`
// does not have a `constructorOnly` or `transient` annotation. A `transient`
// annotation is allowed if the parameter is declared as a `val`. In that case
// the `@transient` has the meaning of Java's transient modifier.
def checkNotTransient(param: Symbol, acc: Symbol) =

// Is parameter accessor `acc` a `private[this] val` parameter? Unlike other `val`
// parameters these do not have the Accessor flag set, in fact they are indistinguishable
// in their flags from simple non-val parameters. To find out, we look instead at the
// definition ValDef and check whether it has an explicit `Private` modifier.
def isPrivateThisVal(acc: Symbol) =
clsStats.exists {
case stat: DefTree =>
stat.symbol == acc && stat.mods.mods.exists(_.flags.is(Private))
case _ =>
false
}

def msg(annot: String) =
em"${acc.name} is marked `$annot` but it is retained as a field in ${acc.owner}"

if param.hasAnnotation(defn.ConstructorOnlyAnnot) then
report.error(msg("@constructorOnly"), acc.srcPos)
else if param.hasAnnotation(defn.TransientAnnot) && !acc.is(Accessor) && !isPrivateThisVal(acc) then
if Feature.migrateTo3 then
report.migrationWarning(msg("@transient") + em"\nYou can avoid the error by declaring the parameter as a `private val`.", acc.srcPos)
else
report.error(msg("@transient"), acc.srcPos)
end checkNotTransient

// The initializers for the retained accessors */
val copyParams = accessors flatMap { acc =>
if (!isRetained(acc)) {
Expand All @@ -256,8 +287,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
}
else {
val param = acc.subst(accessors, paramSyms)
if (param.hasAnnotation(defn.ConstructorOnlyAnnot))
report.error(em"${acc.name} is marked `@constructorOnly` but it is retained as a field in ${acc.owner}", acc.srcPos)
checkNotTransient(param, acc)
val target = if (acc.is(Method)) acc.field else acc
if (!target.exists) Nil // this case arises when the parameter accessor is an alias
else {
Expand Down
7 changes: 4 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
val annotSym = annot.symbol
annotSym.hasAnnotation(metaAnnotSym)
|| annotSym.hasAnnotation(metaAnnotSymBackup)
|| (keepIfNoRelevantAnnot && {
!annotSym.annotations.exists(metaAnnot => defn.FieldAccessorMetaAnnots.contains(metaAnnot.symbol))
})
|| keepIfNoRelevantAnnot
&& !annotSym.annotations.exists(metaAnnot => defn.FieldAccessorMetaAnnots.contains(metaAnnot.symbol))
|| annotSym == defn.TransientAnnot && metaAnnotSym == defn.ParamMetaAnnot
// exception: @transient is marked as @field in Scala 2, but is also usable on class parameters
if sym.annotations.nonEmpty then
sym.filterAnnotations(shouldKeep(_))

Expand Down
35 changes: 35 additions & 0 deletions tests/neg/transient-params.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class Context

class Test1()(implicit @transient ctx: Context) { // error
def test1 = implicitly[Context]
}

class Test2()(@transient ctx: Context) { // error
def test1 = ctx
}

class Test3()(implicit @transient ctx: Context) { // OK
val foo = implicitly[Context]
}

final class Test4(@transient val obj: Any): // OK
def foo = obj

final class Test5(@transient private val obj: Any): // OK
def foo = obj

final class Test6(@transient private[this] val obj: Any): // OK
def foo = obj

final class Test7(@transient obj: Any): // error
def foo = obj










1 change: 1 addition & 0 deletions tests/run/transient-params.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Context(22)
12 changes: 12 additions & 0 deletions tests/run/transient-params.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
case class Context(x: Int)

class Test3()(using @transient ctx: Context) { // OK
println(implicitly[Context])
}

class Test4(@transient private val x: Int) // OK

object Test:
def main(args: Array[String]): Unit =
given Context = Context(22)
Test3()