Skip to content

Commit

Permalink
Fix #4440: Do not serialize the content of static objects
Browse files Browse the repository at this point in the history
In #4450 and Scala 2.12, readResolve is used to make sure deserializing
an object returns the singleton instance of the object, but this doesn't
prevent the fields of the objects from being serialized in the first
place even though they're not used. Scala 2.13 switched to using
writeReplace to completely bypass serialization of the object in
scala/scala#7297. This commit adapts this to
Dotty.

Co-Authored-By: Ingar Abrahamsen <[email protected]>
Co-Authored-By: Jason Zaugg <[email protected]>
  • Loading branch information
2 people authored and smarter committed Jan 25, 2019
1 parent af9264d commit 5db7456
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 22 deletions.
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,9 @@ class Definitions {
case List(pt) => (pt isRef StringClass)
case _ => false
}).symbol.asTerm

lazy val JavaSerializableClass: ClassSymbol = ctx.requiredClass("java.io.Serializable")

lazy val ComparableClass: ClassSymbol = ctx.requiredClass("java.lang.Comparable")

lazy val SystemClass: ClassSymbol = ctx.requiredClass("java.lang.System")
Expand Down Expand Up @@ -650,6 +652,11 @@ class Definitions {
lazy val Product_productPrefixR: TermRef = ProductClass.requiredMethodRef(nme.productPrefix)
def Product_productPrefix(implicit ctx: Context): Symbol = Product_productPrefixR.symbol

lazy val ModuleSerializationProxyType: TypeRef = ctx.requiredClassRef("scala.runtime.ModuleSerializationProxy")
def ModuleSerializationProxyClass(implicit ctx: Context): ClassSymbol = ModuleSerializationProxyType.symbol.asClass
lazy val ModuleSerializationProxyConstructor: TermSymbol =
ModuleSerializationProxyClass.requiredMethod(nme.CONSTRUCTOR, List(ClassType(WildcardType)))

lazy val GenericType: TypeRef = ctx.requiredClassRef("scala.reflect.Generic")
def GenericClass(implicit ctx: Context): ClassSymbol = GenericType.symbol.asClass
lazy val ShapeType: TypeRef = ctx.requiredClassRef("scala.compiletime.Shape")
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ object StdNames {
val withFilterIfRefutable: N = "withFilterIfRefutable$"
val WorksheetWrapper: N = "WorksheetWrapper"
val wrap: N = "wrap"
val writeReplace: N = "writeReplace"
val zero: N = "zero"
val zip: N = "zip"
val nothingRuntimeClass: N = "scala.runtime.Nothing$"
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,10 @@ object SymDenotations {
*/
def derivesFrom(base: Symbol)(implicit ctx: Context): Boolean = false

/** Is this symbol a class that extends `java.io.Serializable` ? */
def isSerializable(implicit ctx: Context): Boolean =
isClass && derivesFrom(defn.JavaSerializableClass)

/** Is this symbol a class that extends `AnyVal`? */
final def isValueClass(implicit ctx: Context): Boolean = {
val di = initial
Expand Down
47 changes: 38 additions & 9 deletions compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import ValueClasses.isDerivedValueClass
* def productArity: Int
* def productPrefix: String
*
* Special handling:
* protected def readResolve(): AnyRef
* Add to serializable static objects, unless an implementation
* already exists:
* private def writeReplace(): AnyRef
*
* Selectively added to value classes, unless a non-default
* implementation already exists:
Expand All @@ -50,8 +51,10 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
def caseSymbols(implicit ctx: Context): List[Symbol] = { initSymbols; myCaseSymbols }
def caseModuleSymbols(implicit ctx: Context): List[Symbol] = { initSymbols; myCaseModuleSymbols }

/** The synthetic methods of the case or value class `clazz`. */
def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = {
/** If this is a case or value class, return the appropriate additional methods,
* otherwise return nothing.
*/
def caseAndValueMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = {
val clazzType = clazz.appliedRef
lazy val accessors =
if (isDerivedValueClass(clazz)) clazz.paramAccessors.take(1) // Tail parameters can only be `erased`
Expand Down Expand Up @@ -255,12 +258,38 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
*/
def canEqualBody(that: Tree): Tree = that.isInstance(AnnotatedType(clazzType, Annotation(defn.UncheckedAnnot)))

symbolsToSynthesize flatMap syntheticDefIfMissing
symbolsToSynthesize.flatMap(syntheticDefIfMissing)
}

def addSyntheticMethods(impl: Template)(implicit ctx: Context): Template =
if (ctx.owner.is(Case) || isDerivedValueClass(ctx.owner))
cpy.Template(impl)(body = impl.body ++ syntheticMethods(ctx.owner.asClass))
/** If this is a serializable static object `Foo`, add the method:
*
* private def writeReplace(): AnyRef =
* new scala.runtime.ModuleSerializationProxy(classOf[Foo$])
*
* unless an implementation already exists, otherwise do nothing.
*/
def serializableObjectMethod(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = {
def hasWriteReplace: Boolean =
clazz.membersNamed(nme.writeReplace)
.filterWithPredicate(s => s.signature == Signature(defn.AnyRefType, isJava = false))
.exists
if (clazz.is(Module) && clazz.isStatic && clazz.isSerializable && !hasWriteReplace) {
val writeReplace = ctx.newSymbol(clazz, nme.writeReplace, Method | Private | Synthetic,
MethodType(Nil, defn.AnyRefType), coord = clazz.coord).entered.asTerm
List(
DefDef(writeReplace,
_ => New(defn.ModuleSerializationProxyType,
defn.ModuleSerializationProxyConstructor,
List(Literal(Constant(clazz.typeRef)))))
.withSpan(ctx.owner.span.focus))
}
else
impl
Nil
}

def addSyntheticMethods(impl: Template)(implicit ctx: Context): Template = {
val clazz = ctx.owner.asClass
cpy.Template(impl)(body = serializableObjectMethod(clazz) ::: caseAndValueMethods(clazz) ::: impl.body)
}

}
54 changes: 54 additions & 0 deletions library/src/scala/runtime/ModuleSerializationProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copied from https://github.com/scala/scala/blob/2.13.x/src/library/scala/runtime/ModuleSerializationProxy.java
// TODO: Remove this file once we switch to the Scala 2.13 stdlib since it already contains it.

/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package scala.runtime;

import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashSet;
import java.util.Set;

/** A serialization proxy for singleton objects */
public final class ModuleSerializationProxy implements Serializable {
private static final long serialVersionUID = 1L;
private final Class<?> moduleClass;
private static final ClassValue<Object> instances = new ClassValue<Object>() {
@Override
protected Object computeValue(Class<?> type) {
try {
return AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> type.getField("MODULE$").get(null));
} catch (PrivilegedActionException e) {
return rethrowRuntime(e.getCause());
}
}
};

private static Object rethrowRuntime(Throwable e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
else throw new RuntimeException(cause);
}

public ModuleSerializationProxy(Class<?> moduleClass) {
this.moduleClass = moduleClass;
}

@SuppressWarnings("unused")
private Object readResolve() {
return instances.get(moduleClass);
}
}
5 changes: 3 additions & 2 deletions tests/pos/simpleCaseObject.decompiled
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Decompiled from out/posTestFromTasty/pos/simpleCaseObject/foo/Foo.class */
/** Decompiled from out/posTestFromTasty/pos/simpleCaseObject/foo/Foo.tasty */
package foo {
case object Foo {
def writeReplace(): scala.AnyRef = new scala.runtime.ModuleSerializationProxy(foo.Foo.getClass())
override def hashCode(): scala.Int = 1045991777
override def toString(): java.lang.String = "Foo"
override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[foo.Foo.type @scala.unchecked()]
Expand All @@ -11,4 +12,4 @@ package foo {
throw new java.lang.IndexOutOfBoundsException(n.toString())
}
}
}
}
6 changes: 3 additions & 3 deletions tests/run/literals.decompiled
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/** Decompiled from out/runTestFromTasty/run/literals/Test.class */
/** Decompiled from out/runTestFromTasty/run/literals/Test.tasty */
object Test {
def αρετη: java.lang.String = "alpha rho epsilon tau eta"
case class GGG(i: scala.Int) {
def αα(that: Test.GGG): scala.Int = GGG.this.i.+(that.i)
override def hashCode(): scala.Int = {
var acc: scala.Int = 767242539
acc = scala.runtime.Statics.mix(acc, GGG.this.i)
Expand All @@ -24,6 +23,7 @@ object Test {
case _ =>
throw new java.lang.IndexOutOfBoundsException(n.toString())
}
def αα(that: Test.GGG): scala.Int = GGG.this.i.+(that.i)
}
object GGG extends scala.Function1[scala.Int, Test.GGG]
def check_success[a](name: scala.Predef.String, closure: => a, expected: a): scala.Unit = {
Expand Down Expand Up @@ -95,4 +95,4 @@ object Test {
val ggg: scala.Int = Test.GGG.apply(1).αα(Test.GGG.apply(2))
Test.check_success[scala.Int]("ggg == 3", ggg, 3)
}
}
}
27 changes: 27 additions & 0 deletions tests/run/module-serialization-proxy-class-unload.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import java.io.File

object Module {
val data = new Array[Byte](32 * 1024 * 1024)
}

object Test {
private val readResolve = classOf[scala.runtime.ModuleSerializationProxy].getDeclaredMethod("readResolve")
readResolve.setAccessible(true)

val testClassesDir = new File(Module.getClass.getClassLoader.getResource("Module.class").toURI).getParentFile
def main(args: Array[String]): Unit = {
for (i <- 1 to 256) {
// This would "java.lang.OutOfMemoryError: Java heap space" if ModuleSerializationProxy
// prevented class unloading.
deserializeDynamicLoadedClass()
}
}

def deserializeDynamicLoadedClass(): Unit = {
val loader = new java.net.URLClassLoader(Array(testClassesDir.toURI.toURL), ClassLoader.getSystemClassLoader)
val moduleClass = loader.loadClass("Module$")
assert(moduleClass ne Module.getClass)
val result = readResolve.invoke(new scala.runtime.ModuleSerializationProxy(moduleClass))
assert(result.getClass == moduleClass)
}
}
14 changes: 14 additions & 0 deletions tests/run/serialize.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,23 @@ object Test {
in.readObject.asInstanceOf[T]
}

object Foo extends Serializable {}

object Baz extends Serializable {
private def writeReplace(): AnyRef = {
this
}
}

def main(args: Array[String]): Unit = {
val x: PartialFunction[Int, Int] = { case x => x + 1 }
val adder = serializeDeserialize(x)
assert(adder(1) == 2)

val foo = serializeDeserialize(Foo)
assert(foo eq Foo)

val baz = serializeDeserialize(Baz)
assert(baz ne Baz)
}
}
2 changes: 1 addition & 1 deletion tests/run/tasty-extractors-2.check
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymb
Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil)), Nil, None, List(DefDef("a", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0))))))), Term.Literal(Constant.Unit())))
Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymbol(<scala>), NoPrefix())))

Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Product"), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("productElementName", Nil, List(List(ValDef("x$1", TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Int"), None))), TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "String"), Some(Term.Match(Term.Ident("x$1"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "IndexOutOfBoundsException")), "<init>"), List(Term.Apply(Term.Select(Term.Select(Term.Select(Term.Ident("java"), "lang"), "String"), "valueOf"), List(Term.Ident("x$1")))))))))))), DefDef("copy", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("hashCode", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Literal(Constant.Int(394005536)))), DefDef("equals", Nil, List(List(ValDef("x$0", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.Apply(Term.Select(Term.This(Some(Id("Foo"))), "eq"), List(Term.TypeApply(Term.Select(Term.Ident("x$0"), "asInstanceOf"), List(TypeTree.Inferred())))), "||"), List(Term.Match(Term.Ident("x$0"), List(CaseDef(Pattern.Bind("x$0", Pattern.TypeTest(TypeTree.Inferred())), None, Term.Literal(Constant.Boolean(true))), CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Boolean(false))))))))), DefDef("toString", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Ident("_toString"), List(Term.This(Some(Id("Foo"))))))), DefDef("canEqual", Nil, List(List(ValDef("that", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.TypeApply(Term.Select(Term.Ident("that"), "isInstanceOf"), List(TypeTree.Inferred())))), DefDef("productArity", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0)))), DefDef("productPrefix", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.String("Foo")))), DefDef("productElement", Nil, List(List(ValDef("n", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Match(Term.Ident("n"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), List(Term.Apply(Term.Select(Term.Ident("n"), "toString"), Nil)))))))))))), ValDef("Foo", TypeTree.Ident("Foo$"), Some(Term.Apply(Term.Select(Term.New(TypeTree.Ident("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Applied(TypeTree.Inferred(), List(TypeTree.Inferred()))), Nil, Some(ValDef("_", TypeTree.Singleton(Term.Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Literal(Constant.Boolean(true))))))), Term.Literal(Constant.Unit())))
Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Product"), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("hashCode", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Literal(Constant.Int(394005536)))), DefDef("equals", Nil, List(List(ValDef("x$0", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.Apply(Term.Select(Term.This(Some(Id("Foo"))), "eq"), List(Term.TypeApply(Term.Select(Term.Ident("x$0"), "asInstanceOf"), List(TypeTree.Inferred())))), "||"), List(Term.Match(Term.Ident("x$0"), List(CaseDef(Pattern.Bind("x$0", Pattern.TypeTest(TypeTree.Inferred())), None, Term.Literal(Constant.Boolean(true))), CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Boolean(false))))))))), DefDef("toString", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Ident("_toString"), List(Term.This(Some(Id("Foo"))))))), DefDef("canEqual", Nil, List(List(ValDef("that", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.TypeApply(Term.Select(Term.Ident("that"), "isInstanceOf"), List(TypeTree.Inferred())))), DefDef("productArity", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0)))), DefDef("productPrefix", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.String("Foo")))), DefDef("productElement", Nil, List(List(ValDef("n", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Match(Term.Ident("n"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), List(Term.Apply(Term.Select(Term.Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", Nil, List(List(ValDef("x$1", TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Int"), None))), TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "String"), Some(Term.Match(Term.Ident("x$1"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "IndexOutOfBoundsException")), "<init>"), List(Term.Apply(Term.Select(Term.Select(Term.Select(Term.Ident("java"), "lang"), "String"), "valueOf"), List(Term.Ident("x$1")))))))))))), DefDef("copy", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))))), ValDef("Foo", TypeTree.Ident("Foo$"), Some(Term.Apply(Term.Select(Term.New(TypeTree.Ident("Foo$")), "<init>"), Nil))), ClassDef("Foo$", DefDef("<init>", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil), TypeTree.Applied(TypeTree.Inferred(), List(TypeTree.Inferred()))), Nil, Some(ValDef("_", TypeTree.Singleton(Term.Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Literal(Constant.Boolean(true))))))), Term.Literal(Constant.Unit())))
Type.SymRef(IsClassSymbol(<scala.Unit>), Type.ThisType(Type.SymRef(IsPackageSymbol(<scala>), NoPrefix())))

Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo1", DefDef("<init>", Nil, List(List(ValDef("a", TypeTree.Ident("Int"), None))), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), "<init>"), Nil)), Nil, None, List(ValDef("a", TypeTree.Inferred(), None)))), Term.Literal(Constant.Unit())))
Expand Down
14 changes: 7 additions & 7 deletions tests/run/valueclasses-pavlov.decompiled
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Box1.class */
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Box1.tasty */
final class Box1(val value: scala.Predef.String) extends scala.AnyVal() {
override def hashCode(): scala.Int = Box1.this.value.hashCode()
override def equals(x$0: scala.Any): scala.Boolean = x$0 match {
Expand All @@ -9,32 +9,32 @@ final class Box1(val value: scala.Predef.String) extends scala.AnyVal() {
}
}
object Box1 extends scala.AnyRef()
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Box2.class */
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Box2.tasty */
final class Box2(val value: scala.Predef.String) extends scala.AnyVal() with Foo {
def box1(x: Box1): scala.Predef.String = "box1: ok"
def box2(x: Box2): scala.Predef.String = "box2: ok"
override def hashCode(): scala.Int = Box2.this.value.hashCode()
override def equals(x$0: scala.Any): scala.Boolean = x$0 match {
case x$0: Box2 @scala.unchecked() =>
Box2.this.value.==(x$0.value)
case _ =>
false
}
def box1(x: Box1): scala.Predef.String = "box1: ok"
def box2(x: Box2): scala.Predef.String = "box2: ok"
}
object Box2 extends scala.AnyRef()
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/C.class */
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/C.tasty */
class C(x: scala.Predef.String) {
def this() = {
this("")
()
}
}
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Foo.class */
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Foo.tasty */
trait Foo() extends scala.Any {
def box1(x: Box1): scala.Predef.String
def box2(x: Box2): scala.Predef.String
}
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Test.class */
/** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Test.tasty */
object Test {
def main(args: scala.Array[scala.Predef.String]): scala.Unit = {
val b1: Box1 = new Box1("")
Expand Down

0 comments on commit 5db7456

Please sign in to comment.