diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 6f8aed16fdc3..53d51077e967 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -56,7 +56,8 @@ class Compiler { List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes - new CookComments) :: // Cook the comments: expand variables, doc, etc. + new CookComments, // Cook the comments: expand variables, doc, etc. + new CompleteJavaEnums) :: // Fill in constructors for Java enums List(new CheckStatic, // Check restrictions that apply to @static members new ElimRepeated, // Rewrite vararg parameters and arguments new ExpandSAMs, // Expand single abstract method closures to anonymous classes diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index f6622eedd04b..4a363a14e86a 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -596,6 +596,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => loop(tree, Nil, Nil) } + /** Decompose a template body into parameters and other statements */ + def decomposeTemplateBody(body: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) = + body.partition { + case stat: TypeDef => stat.symbol is Flags.Param + case stat: ValOrDefDef => + stat.symbol.is(Flags.ParamAccessor) && !stat.symbol.isSetter + case _ => false + } + /** An extractor for closures, either contained in a block or standalone. */ object closure { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2d4b2b9ce1f3..95320e5e91f1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -299,7 +299,6 @@ class Definitions { val companion = JavaLangPackageVal.info.decl(nme.Object).symbol companion.moduleClass.info = NoType // to indicate that it does not really exist companion.info = NoType // to indicate that it does not really exist - completeClass(cls) } def ObjectType: TypeRef = ObjectClass.typeRef @@ -674,6 +673,8 @@ class Definitions { def NoneClass(implicit ctx: Context): ClassSymbol = NoneModuleRef.symbol.moduleClass.asClass lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum") def EnumClass(implicit ctx: Context): ClassSymbol = EnumType.symbol.asClass + lazy val JEnumType: TypeRef = ctx.requiredClassRef("scala.compat.JEnum") + def JEnumClass(implicit ctx: Context): ClassSymbol = JEnumType.symbol.asClass lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues") def EnumValuesClass(implicit ctx: Context): ClassSymbol = EnumValuesType.symbol.asClass lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product") diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index e992a53a4d1b..17d22d23803c 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -814,8 +814,11 @@ object Denotations { def invalidateInheritedInfo(): Unit = () private def updateValidity()(implicit ctx: Context): this.type = { - assert(ctx.runId >= validFor.runId || ctx.settings.YtestPickler.value, // mixing test pickler with debug printing can travel back in time - s"denotation $this invalid in run ${ctx.runId}. ValidFor: $validFor") + assert( + ctx.runId >= validFor.runId || + ctx.settings.YtestPickler.value || // mixing test pickler with debug printing can travel back in time + symbol.is(Permanent), // Permanent symbols are valid in all runIds + s"denotation $this invalid in run ${ctx.runId}. ValidFor: $validFor") var d: SingleDenotation = this do { d.validFor = Period(ctx.period.runId, d.validFor.firstPhaseId, d.validFor.lastPhaseId) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index cf3352ac6c4c..efb31dfab3ef 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -490,6 +490,7 @@ object TastyFormat { | STATIC | OBJECT | TRAIT + | ENUM | LOCAL | SYNTHETIC | ARTIFACT diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index dbb24b42076c..1ed294163ea6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -509,12 +509,7 @@ class TreePickler(pickler: TastyPickler) { case tree: Template => registerDef(tree.symbol) writeByte(TEMPLATE) - val (params, rest) = tree.body partition { - case stat: TypeDef => stat.symbol is Flags.Param - case stat: ValOrDefDef => - stat.symbol.is(Flags.ParamAccessor) && !stat.symbol.isSetter - case _ => false - } + val (params, rest) = decomposeTemplateBody(tree.body) withLength { pickleParams(params) tree.parents.foreach(pickleTree) @@ -635,44 +630,48 @@ class TreePickler(pickler: TastyPickler) { def pickleFlags(flags: FlagSet, isTerm: Boolean)(implicit ctx: Context): Unit = { import Flags._ - if (flags is Private) writeByte(PRIVATE) - if (flags is Protected) writeByte(PROTECTED) - if (flags.is(Final, butNot = Module)) writeByte(FINAL) - if (flags is Case) writeByte(CASE) - if (flags is Override) writeByte(OVERRIDE) - if (flags is Inline) writeByte(INLINE) - if (flags is InlineProxy) writeByte(INLINEPROXY) - if (flags is Macro) writeByte(MACRO) - if (flags is JavaStatic) writeByte(STATIC) - if (flags is Module) writeByte(OBJECT) - if (flags is Enum) writeByte(ENUM) - if (flags is Local) writeByte(LOCAL) - if (flags is Synthetic) writeByte(SYNTHETIC) - if (flags is Artifact) writeByte(ARTIFACT) - if (flags is Scala2x) writeByte(SCALA2X) + def writeModTag(tag: Int) = { + assert(isModifierTag(tag)) + writeByte(tag) + } + if (flags is Private) writeModTag(PRIVATE) + if (flags is Protected) writeModTag(PROTECTED) + if (flags.is(Final, butNot = Module)) writeModTag(FINAL) + if (flags is Case) writeModTag(CASE) + if (flags is Override) writeModTag(OVERRIDE) + if (flags is Inline) writeModTag(INLINE) + if (flags is InlineProxy) writeModTag(INLINEPROXY) + if (flags is Macro) writeModTag(MACRO) + if (flags is JavaStatic) writeModTag(STATIC) + if (flags is Module) writeModTag(OBJECT) + if (flags is Enum) writeModTag(ENUM) + if (flags is Local) writeModTag(LOCAL) + if (flags is Synthetic) writeModTag(SYNTHETIC) + if (flags is Artifact) writeModTag(ARTIFACT) + if (flags is Scala2x) writeModTag(SCALA2X) if (isTerm) { - if (flags is Implicit) writeByte(IMPLICIT) - if (flags is Implied) writeByte(IMPLIED) - if (flags is Erased) writeByte(ERASED) - if (flags.is(Lazy, butNot = Module)) writeByte(LAZY) - if (flags is AbsOverride) { writeByte(ABSTRACT); writeByte(OVERRIDE) } - if (flags is Mutable) writeByte(MUTABLE) - if (flags is Accessor) writeByte(FIELDaccessor) - if (flags is CaseAccessor) writeByte(CASEaccessor) - if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) - if (flags is StableRealizable) writeByte(STABLE) - if (flags is Extension) writeByte(EXTENSION) - if (flags is Given) writeByte(GIVEN) - if (flags is ParamAccessor) writeByte(PARAMsetter) - if (flags is Exported) writeByte(EXPORTED) + if (flags is Implicit) writeModTag(IMPLICIT) + if (flags is Implied) writeModTag(IMPLIED) + if (flags is Erased) writeModTag(ERASED) + if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY) + if (flags is AbsOverride) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) } + if (flags is Mutable) writeModTag(MUTABLE) + if (flags is Accessor) writeModTag(FIELDaccessor) + if (flags is CaseAccessor) writeModTag(CASEaccessor) + if (flags is DefaultParameterized) writeModTag(DEFAULTparameterized) + if (flags is StableRealizable) writeModTag(STABLE) + if (flags is Extension) writeModTag(EXTENSION) + if (flags is Given) writeModTag(GIVEN) + if (flags is ParamAccessor) writeModTag(PARAMsetter) + if (flags is Exported) writeModTag(EXPORTED) assert(!(flags is Label)) } else { - if (flags is Sealed) writeByte(SEALED) - if (flags is Abstract) writeByte(ABSTRACT) - if (flags is Trait) writeByte(TRAIT) - if (flags is Covariant) writeByte(COVARIANT) - if (flags is Contravariant) writeByte(CONTRAVARIANT) - if (flags is Opaque) writeByte(OPAQUE) + if (flags is Sealed) writeModTag(SEALED) + if (flags is Abstract) writeModTag(ABSTRACT) + if (flags is Trait) writeModTag(TRAIT) + if (flags is Covariant) writeModTag(COVARIANT) + if (flags is Contravariant) writeModTag(CONTRAVARIANT) + if (flags is Opaque) writeModTag(OPAQUE) } } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 5656baacaaf1..d77d4a0bf25d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -851,7 +851,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else if (sym.is(ModuleClass)) nameString(sym.name.stripModuleClassSuffix) else if (hasMeaninglessName(sym)) - simpleNameString(sym.owner) + simpleNameString(sym.owner) + idString(sym) else nameString(sym) (keywordText(kindString(sym)) ~~ { diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala new file mode 100644 index 000000000000..14e1bd065f39 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -0,0 +1,161 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import StdNames.{nme, tpnme} +import Types._ +import dotty.tools.dotc.transform.MegaPhase._ +import Flags._ +import Contexts.Context +import Symbols._ +import Constants._ +import Decorators._ +import DenotTransformers._ + +object CompleteJavaEnums { + val name: String = "completeJavaEnums" + + private val nameParamName: TermName = "$name".toTermName + private val ordinalParamName: TermName = "$ordinal".toTermName +} + +/** For Scala enums that inherit from java.lang.Enum: + * Add constructor parameters for `name` and `ordinal` to pass from each + * case to the java.lang.Enum class. + */ +class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => + import CompleteJavaEnums._ + import ast.tpd._ + + override def phaseName: String = CompleteJavaEnums.name + + override def relaxedTypingInGroup: Boolean = true + // 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) + else tp + + /** Is `sym` a Scala enum class that derives (directly) from `java.lang.Enum`? + */ + private def derivesFromJEnum(sym: Symbol)(implicit ctx: Context) = + sym.is(Enum, butNot = Case) && + sym.info.parents.exists(p => p.typeSymbol == defn.JEnumClass) + + /** Add constructor parameters `$name: String` and `$ordinal: Int` to the end of + * the last parameter list of (method- or poly-) type `tp`. + */ + private def addConstrParams(tp: Type)(implicit ctx: Context): Type = tp match { + case tp: PolyType => + tp.derivedLambdaType(resType = addConstrParams(tp.resType)) + case tp: MethodType => + tp.resType match { + case restpe: MethodType => + tp.derivedLambdaType(resType = addConstrParams(restpe)) + case _ => + tp.derivedLambdaType( + paramNames = tp.paramNames ++ List(nameParamName, ordinalParamName), + paramInfos = tp.paramInfos ++ List(defn.StringType, defn.IntType)) + } + } + + /** The list of parameter definitions `$name: String, $ordinal: Int`, in given `owner` + * with given flags (either `Param` or `ParamAccessor`) + */ + private def addedParams(owner: Symbol, flag: FlagSet)(implicit ctx: Context): List[ValDef] = { + val nameParam = ctx.newSymbol(owner, nameParamName, flag | Synthetic, defn.StringType, coord = owner.span) + val ordinalParam = ctx.newSymbol(owner, ordinalParamName, flag | Synthetic, defn.IntType, coord = owner.span) + List(ValDef(nameParam), ValDef(ordinalParam)) + } + + /** Add arguments `args` to the parent constructor application in `parents` that invokes + * a constructor of `targetCls`, + */ + private def addEnumConstrArgs(targetCls: Symbol, parents: List[Tree], args: List[Tree])(implicit ctx: Context): List[Tree] = + parents.map { + case app @ Apply(fn, args0) if fn.symbol.owner == targetCls => cpy.Apply(app)(fn, args0 ++ args) + case p => p + } + + /** 1. If this is a constructor of a enum class that extends, add $name and $ordinal parameters to it. + * + * 2. If this is a $new method that creates simple cases, pass $name and $ordinal parameters + * to the enum superclass. The $new method looks like this: + * + * def $new(..., enumTag: Int, name: String) = { + * class $anon extends E(...) { ... } + * new $anon + * } + * + * After the transform it is expanded to + * + * def $new(..., enumTag: Int, name: String) = { + * class $anon extends E(..., name, enumTag) { ... } + * new $anon + * } + */ + override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = { + val sym = tree.symbol + if (sym.isConstructor && derivesFromJEnum(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)) { + 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)( + parents = addEnumConstrArgs(sym.owner.linkedClass, templ.parents, args)) + cpy.DefDef(tree)( + rhs = cpy.Block(tree.rhs)(cpy.TypeDef(tdef)(tdef.name, templ1) :: Nil, call)) + } + else tree + } + + /** 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. + * + * 2. If this is an anonymous class that implement a value enum case, + * pass $name and $ordinal parameters to the enum superclass. The class + * looks like this: + * + * class $anon extends E(...) { + * ... + * def enumTag = N + * def toString = S + * ... + * } + * + * After the transform it is expanded to + * + * class $anon extends E(..., N, S) { + * "same as before" + * } + */ + override def transformTemplate(templ: Template)(implicit ctx: Context): Template = { + val cls = templ.symbol.owner + if (derivesFromJEnum(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, + body = params ++ addedDefs ++ rest) + } + else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && derivesFromJEnum(cls.owner.owner.linkedClass)) { + def rhsOf(name: TermName) = + templ.body.collect { + case mdef: DefDef if mdef.name == name => mdef.rhs + }.head + val args = List(rhsOf(nme.toString_), rhsOf(nme.enumTag)) + cpy.Template(templ)( + parents = addEnumConstrArgs(cls.owner.owner.linkedClass, templ.parents, args)) + } + else templ + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7e94b41b3452..629ce7b953b6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1046,20 +1046,26 @@ trait Checking { ctx.error(em"$what $msg", posd.sourcePos) } - /** Check that all case classes that extend `scala.Enum` are `enum` cases */ + /** 1. Check that all case classes that extend `scala.Enum` are `enum` cases + * 2. Check that case class `enum` cases do not extend java.lang.Enum. + */ def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(implicit ctx: Context): Unit = { import untpd.modsDeco def isEnumAnonCls = cls.isAnonymousClass && cls.owner.isTerm && (cls.owner.flagsUNSAFE.is(Case) || cls.owner.name == nme.DOLLAR_NEW) - if (!cdef.mods.isEnumCase && !isEnumAnonCls) { - // Since enums are classes and Namer checks that classes don't extend multiple classes, we only check the class - // parent. - // - // Unlike firstParent.derivesFrom(defn.EnumClass), this test allows inheriting from `Enum` by hand; - // see enum-List-control.scala. - if (cls.is(Case) || firstParent.is(Enum)) + 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) + } + 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 + // parent. + // + // Unlike firstParent.derivesFrom(defn.EnumClass), this test allows inheriting from `Enum` by hand; + // see enum-List-control.scala. ctx.error(ClassCannotExtendEnum(cls, firstParent), cdef.sourcePos) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 41fde927164e..d7ea2549774d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -498,23 +498,9 @@ class Namer { typer: Typer => recur(expanded(origStat)) } - /** Determines whether this field holds an enum constant. - * To qualify, the following conditions must be met: - * - The field's class has the ENUM flag set - * - The field's class extends java.lang.Enum - * - The field has the ENUM flag set - * - The field is static - * - The field is stable - */ - def isEnumConstant(vd: ValDef)(implicit ctx: Context): Boolean = { - // val ownerHasEnumFlag = - // Necessary to check because scalac puts Java's static members into the companion object - // while Scala's enum constants live directly in the class. - // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal - // cyclic reference error. See the commit message for details. - // if (ctx.compilationUnit.isJava) ctx.owner.companionClass.is(Enum) else ctx.owner.is(Enum) - vd.mods.is(JavaEnumValue) // && ownerHasEnumFlag - } + /** Determines whether this field holds an enum constant. */ + def isEnumConstant(vd: ValDef)(implicit ctx: Context): Boolean = + vd.mods.is(JavaEnumValue) /** Add child annotation for `child` to annotations of `cls`. The annotation * is added at the correct insertion point, so that Child annotations appear diff --git a/library/src/scala/compat/JEnum.scala b/library/src/scala/compat/JEnum.scala new file mode 100644 index 000000000000..d2e696534d4b --- /dev/null +++ b/library/src/scala/compat/JEnum.scala @@ -0,0 +1,9 @@ +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 new file mode 100644 index 000000000000..5cc33fae7de5 --- /dev/null +++ b/tests/neg/enum-constrs.scala @@ -0,0 +1,5 @@ + +enum E[+T] extends compat.JEnum[E[_]] { + case S1, S2 + case C() extends E[Int] // error: parameterized case is not allowed +} diff --git a/tests/run/enum-constrs.check b/tests/run/enum-constrs.check new file mode 100644 index 000000000000..668ec8702ae8 --- /dev/null +++ b/tests/run/enum-constrs.check @@ -0,0 +1,3 @@ +Red +S1 +Car diff --git a/tests/run/enum-constrs.scala b/tests/run/enum-constrs.scala new file mode 100644 index 000000000000..b1988f91771c --- /dev/null +++ b/tests/run/enum-constrs.scala @@ -0,0 +1,19 @@ +enum Color extends compat.JEnum[Color] { + case Red, Green, Blue +} + +enum E[+T] extends compat.JEnum[E[_]] { + case S1, S2 + case C extends E[Int] +} + +enum Vehicle(wheels: Int) extends compat.JEnum[Vehicle] { + case Bike extends Vehicle(2) + case Car extends Vehicle(4) +} + +object Test extends App { + println(Color.Red) + println(E.S1) + println(Vehicle.Car) +} \ No newline at end of file