Skip to content

Commit 809c6b3

Browse files
smarterretronym
andcommitted
Fix #10038: Always emit mixin forwarders for readResolve/writeReplace
Adapted from scala/scala#9253. Just like in Scala 2 this isn't a big deal since `-Xmixin-force-forwarders:false` is not the default and not commonly used. Co-Authored-By: Jason Zaugg <[email protected]>
1 parent c2abb49 commit 809c6b3

File tree

4 files changed

+65
-4
lines changed

4 files changed

+65
-4
lines changed

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

+12-4
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,23 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
5757

5858
def needsDisambiguation = competingMethods.exists(x=> !x.is(Deferred)) // multiple implementations are available
5959
def hasNonInterfaceDefinition = competingMethods.exists(!_.owner.is(Trait)) // there is a definition originating from class
60+
61+
// JUnit 4 won't recognize annotated default methods, so always generate a forwarder for them.
62+
def generateJUnitForwarder: Boolean =
63+
meth.annotations.nonEmpty && JUnit4Annotations.exists(annot => meth.hasAnnotation(annot)) &&
64+
ctx.settings.mixinForwarderChoices.isAtLeastJunit
65+
66+
// Similarly, Java serialization won't take into account a readResolve/writeReplace default method.
67+
def generateSerializationForwarder: Boolean =
68+
(meth.name == nme.readResolve || meth.name == nme.writeReplace) && meth.info.paramNamess.flatten.isEmpty
69+
6070
!meth.isConstructor &&
6171
meth.is(Method, butNot = PrivateOrAccessorOrDeferred) &&
62-
(ctx.settings.mixinForwarderChoices.isTruthy || meth.owner.is(Scala2x) || needsDisambiguation || hasNonInterfaceDefinition || needsJUnit4Fix(meth)) &&
72+
(ctx.settings.mixinForwarderChoices.isTruthy || meth.owner.is(Scala2x) || needsDisambiguation || hasNonInterfaceDefinition ||
73+
generateJUnitForwarder || generateSerializationForwarder) &&
6374
isCurrent(meth)
6475
}
6576

66-
private def needsJUnit4Fix(meth: Symbol): Boolean =
67-
meth.annotations.nonEmpty && JUnit4Annotations.exists(annot => meth.hasAnnotation(annot)) &&
68-
ctx.settings.mixinForwarderChoices.isAtLeastJunit
6977

7078
final val PrivateOrAccessor: FlagSet = Private | Accessor
7179
final val PrivateOrAccessorOrDeferred: FlagSet = Private | Accessor | Deferred

compiler/test/dotty/tools/dotc/CompilationTests.scala

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ class CompilationTests {
179179
compileFile("tests/run-custom-args/i5256.scala", allowDeepSubtypes),
180180
compileFile("tests/run-custom-args/fors.scala", defaultOptions.and("-source", "3.1")),
181181
compileFile("tests/run-custom-args/no-useless-forwarders.scala", defaultOptions and "-Xmixin-force-forwarders:false"),
182+
compileFile("tests/run-custom-args/defaults-serizaliable-no-forwarders.scala", defaultOptions and "-Xmixin-force-forwarders:false"),
182183
compileFilesInDir("tests/run-custom-args/erased", defaultOptions.and("-Yerased-terms")),
183184
compileFilesInDir("tests/run-deep-subtype", allowDeepSubtypes),
184185
compileFilesInDir("tests/run", defaultOptions)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
2+
3+
trait T1 extends Serializable {
4+
def writeReplace(): AnyRef = new SerializationProxy(this.asInstanceOf[C].s)
5+
}
6+
trait T2 {
7+
def readResolve: AnyRef = new C(this.asInstanceOf[SerializationProxy].s.toLowerCase)
8+
}
9+
class C(val s: String) extends T1
10+
class SerializationProxy(val s: String) extends T2 with Serializable
11+
12+
object Test {
13+
def serializeDeserialize[T <: AnyRef](obj: T) = {
14+
val buffer = new ByteArrayOutputStream
15+
val out = new ObjectOutputStream(buffer)
16+
out.writeObject(obj)
17+
val in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray))
18+
in.readObject.asInstanceOf[T]
19+
}
20+
21+
def main(args: Array[String]): Unit = {
22+
val c1 = new C("TEXT")
23+
val c2 = serializeDeserialize(c1)
24+
assert(c2.s == "text")
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
2+
3+
trait T1 extends Serializable {
4+
def writeReplace(): AnyRef = new SerializationProxy(this.asInstanceOf[C].s)
5+
}
6+
trait T2 {
7+
def readResolve: AnyRef = new C(this.asInstanceOf[SerializationProxy].s.toLowerCase)
8+
}
9+
class C(val s: String) extends T1
10+
class SerializationProxy(val s: String) extends T2 with Serializable
11+
12+
object Test {
13+
def serializeDeserialize[T <: AnyRef](obj: T) = {
14+
val buffer = new ByteArrayOutputStream
15+
val out = new ObjectOutputStream(buffer)
16+
out.writeObject(obj)
17+
val in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray))
18+
in.readObject.asInstanceOf[T]
19+
}
20+
21+
def main(args: Array[String]): Unit = {
22+
val c1 = new C("TEXT")
23+
val c2 = serializeDeserialize(c1)
24+
assert(c2.s == "text")
25+
}
26+
}

0 commit comments

Comments
 (0)