From 0a68c77f023456f0829f7d4423779fda6c783a79 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 6 Jun 2019 17:15:00 +0200 Subject: [PATCH] Re-introduce java.lang.Enum constructor hijack. But this time do it lazily, by installing a special completer. --- .../dotty/tools/dotc/core/Definitions.scala | 31 +++++++++++++++++-- .../tools/dotc/parsing/JavaParsers.scala | 3 +- .../dotc/transform/CompleteJavaEnums.scala | 27 +++++++--------- .../src/dotty/tools/dotc/typer/Checking.scala | 4 +-- library/src/scala/compat/JEnum.scala | 9 ------ tests/neg/enum-constrs.scala | 2 +- tests/run/enum-constrs.scala | 6 ++-- 7 files changed, 47 insertions(+), 35 deletions(-) delete mode 100644 library/src/scala/compat/JEnum.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9bfa280fcc20..b074193c2c05 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -614,6 +614,33 @@ class Definitions { JavaSerializableClass.typeRef else ctx.requiredClassRef("scala.Serializable") + + @threadUnsafe lazy val JavaEnumClass: ClassSymbol = { + val cls = ctx.requiredClass("java.lang.Enum") + cls.infoOrCompleter match { + case completer: ClassfileLoader => + cls.info = new ClassfileLoader(completer.classfile) { + override def complete(root: SymDenotation)(implicit ctx: Context): Unit = { + super.complete(root) + val constr = cls.primaryConstructor + val newInfo = constr.info match { + case info: PolyType => + info.resType match { + case meth: MethodType => + info.derivedLambdaType( + resType = meth.derivedLambdaType( + paramNames = Nil, paramInfos = Nil)) + } + } + constr.info = newInfo + constr.termRef.recomputeDenot() + } + } + cls + } + } + def JavaEnumType = JavaEnumClass.typeRef + def SerializableClass(implicit ctx: Context): ClassSymbol = SerializableType.symbol.asClass @threadUnsafe lazy val StringBuilderType: TypeRef = ctx.requiredClassRef("scala.collection.mutable.StringBuilder") def StringBuilderClass(implicit ctx: Context): ClassSymbol = StringBuilderType.symbol.asClass @@ -674,8 +701,6 @@ class Definitions { def NoneClass(implicit ctx: Context): ClassSymbol = NoneModuleRef.symbol.moduleClass.asClass @threadUnsafe lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum") def EnumClass(implicit ctx: Context): ClassSymbol = EnumType.symbol.asClass - @threadUnsafe lazy val JEnumType: TypeRef = ctx.requiredClassRef("scala.compat.JEnum") - def JEnumClass(implicit ctx: Context): ClassSymbol = JEnumType.symbol.asClass @threadUnsafe lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues") def EnumValuesClass(implicit ctx: Context): ClassSymbol = EnumValuesType.symbol.asClass @threadUnsafe lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product") @@ -1472,7 +1497,7 @@ class Definitions { ScalaPackageClass.enter(m) // force initialization of every symbol that is synthesized or hijacked by the compiler - val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() + val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() :+ JavaEnumClass isInitialized = true } diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 6601d74f2c81..692d99b053b0 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -832,8 +832,7 @@ object JavaParsers { AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType)) */ val superclazz = Apply(TypeApply( - Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), - List(Literal(Constant(null)),Literal(Constant(0)))) + Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), Nil) val enumclazz = atSpan(start, nameOffset) { TypeDef(name, makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.JavaEnum) diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala index 1b18e7c8b0b4..b42fb3b14b24 100644 --- a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -35,14 +35,17 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => // Because it adds additional parameters to some constructors def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = - if (sym.isConstructor && derivesFromJEnum(sym.owner)) addConstrParams(sym.info) + if (sym.isConstructor && ( + sym == defn.JavaEnumClass.primaryConstructor || + derivesFromJavaEnum(sym.owner))) + addConstrParams(sym.info) else tp /** Is `sym` a Scala enum class that derives (directly) from `java.lang.Enum`? */ - private def derivesFromJEnum(sym: Symbol)(implicit ctx: Context) = + private def derivesFromJavaEnum(sym: Symbol)(implicit ctx: Context) = sym.is(Enum, butNot = Case) && - sym.info.parents.exists(p => p.typeSymbol == defn.JEnumClass) + sym.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass) /** Add constructor parameters `$name: String` and `$ordinal: Int` to the end of * the last parameter list of (method- or poly-) type `tp`. @@ -98,10 +101,10 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => */ override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = { val sym = tree.symbol - if (sym.isConstructor && derivesFromJEnum(sym.owner)) + if (sym.isConstructor && derivesFromJavaEnum(sym.owner)) cpy.DefDef(tree)( vparamss = tree.vparamss.init :+ (tree.vparamss.last ++ addedParams(sym, Param))) - else if (sym.name == nme.DOLLAR_NEW && derivesFromJEnum(sym.owner.linkedClass)) { + else if (sym.name == nme.DOLLAR_NEW && derivesFromJavaEnum(sym.owner.linkedClass)) { val Block((tdef @ TypeDef(tpnme.ANON_CLASS, templ: Template)) :: Nil, call) = tree.rhs val args = tree.vparamss.last.takeRight(2).map(param => ref(param.symbol)).reverse val templ1 = cpy.Template(templ)( @@ -113,8 +116,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => } /** 1. If this is an enum class, add $name and $ordinal parameters to its - * parameter accessors and pass them on to the java.lang.Enum constructor, - * replacing the dummy arguments that were passed before. + * parameter accessors and pass them on to the java.lang.Enum constructor. * * 2. If this is an anonymous class that implement a value enum case, * pass $name and $ordinal parameters to the enum superclass. The class @@ -135,20 +137,15 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => */ override def transformTemplate(templ: Template)(implicit ctx: Context): Template = { val cls = templ.symbol.owner - if (derivesFromJEnum(cls)) { + if (derivesFromJavaEnum(cls)) { val (params, rest) = decomposeTemplateBody(templ.body) val addedDefs = addedParams(cls, ParamAccessor) val addedSyms = addedDefs.map(_.symbol.entered) - val parents1 = templ.parents.map { - case app @ Apply(fn, _) if fn.symbol.owner == defn.JEnumClass => - cpy.Apply(app)(fn, addedSyms.map(ref)) - case p => p - } cpy.Template(templ)( - parents = parents1, + parents = addEnumConstrArgs(defn.JavaEnumClass, templ.parents, addedSyms.map(ref)), body = params ++ addedDefs ++ rest) } - else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && derivesFromJEnum(cls.owner.owner.linkedClass)) { + else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && derivesFromJavaEnum(cls.owner.owner.linkedClass)) { def rhsOf(name: TermName) = templ.body.collect { case mdef: DefDef if mdef.name == name => mdef.rhs diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7755eb39122c..f1ad620ae180 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1071,8 +1071,8 @@ trait Checking { (cls.owner.flagsUNSAFE.is(Case) || cls.owner.name == nme.DOLLAR_NEW) if (!isEnumAnonCls) { if (cdef.mods.isEnumCase) { - if (cls.derivesFrom(defn.JEnumClass)) - ctx.error(em"parameterized case is not allowed in an enum that extends java.lang.Enum", cdef.sourcePos) + if (cls.derivesFrom(defn.JavaEnumClass)) + ctx.error(em"paramerized case is not allowed in an enum that extends java.lang.Enum", cdef.sourcePos) } else if (cls.is(Case) || firstParent.is(Enum)) // Since enums are classes and Namer checks that classes don't extend multiple classes, we only check the class diff --git a/library/src/scala/compat/JEnum.scala b/library/src/scala/compat/JEnum.scala deleted file mode 100644 index d2e696534d4b..000000000000 --- a/library/src/scala/compat/JEnum.scala +++ /dev/null @@ -1,9 +0,0 @@ -package scala.compat - -/** A base class to be used for Scala enums that should be also exposed - * as Java enums. - */ -abstract class JEnum[E <: java.lang.Enum[E]]( - name: String = throw new UnsupportedOperationException, // Compiler will pass actual values for these - ordinal: Int = throw new UnsupportedOperationException) // when JEnum is inherited in an enum -extends java.lang.Enum[E](name, ordinal) \ No newline at end of file diff --git a/tests/neg/enum-constrs.scala b/tests/neg/enum-constrs.scala index 5cc33fae7de5..13c39628044d 100644 --- a/tests/neg/enum-constrs.scala +++ b/tests/neg/enum-constrs.scala @@ -1,5 +1,5 @@ -enum E[+T] extends compat.JEnum[E[_]] { +enum E[+T] extends java.lang.Enum[E[_]] { case S1, S2 case C() extends E[Int] // error: parameterized case is not allowed } diff --git a/tests/run/enum-constrs.scala b/tests/run/enum-constrs.scala index b1988f91771c..1418f39b99b2 100644 --- a/tests/run/enum-constrs.scala +++ b/tests/run/enum-constrs.scala @@ -1,13 +1,13 @@ -enum Color extends compat.JEnum[Color] { +enum Color extends java.lang.Enum[Color] { case Red, Green, Blue } -enum E[+T] extends compat.JEnum[E[_]] { +enum E[+T] extends java.lang.Enum[E[_]] { case S1, S2 case C extends E[Int] } -enum Vehicle(wheels: Int) extends compat.JEnum[Vehicle] { +enum Vehicle(wheels: Int) extends java.lang.Enum[Vehicle] { case Bike extends Vehicle(2) case Car extends Vehicle(4) }