Skip to content

Commit a90f39c

Browse files
committed
SI-8058 Better support for enum trees
Replace the approach of “detect some magic combination of flags to trigger some action” by introducing an enum flag which makes the semantics and the intentions of the code using it more explicit. This basically involves wiring up the existing ACC_ENUM bit to a new modifier flag and making sure it is set correctly when encountering enums. The existing enum tests files/pos/t5165 and files/pos/t2764 keep working, showing that this hasn't introduced any obvious regressions. Tests for the changes in Namer which prevent scalac from messing with enum trees can't be added yet, because one currently can't define an enum in Scala without the help of the macro paradise. The intention is to add the @enum macro as a full test suite as soon as one can depend on macro annotations. One might wonder why we don't check for clazz.superClass == JavaEnumClass (where clazz is the owning class) in isEnumConstant. The reason is that this causes illegal cyclic reference error. Explanation by Eugene why this happens: (23:17:52) xeno_by: so here's what happens as far as I can understand at 11pm :) (23:18:09) xeno_by: scalac tries to complete the signature of the newly expanded class (23:18:11) xeno_by: to do that (23:18:40) xeno_by: to do that it needs three things (23:18:51) xeno_by: because the signature of a class is ClassInfoType which consists of three things (23:19:05) xeno_by: parents (23:19:08) xeno_by: decls (23:19:09) xeno_by: and symbol (23:19:20) xeno_by: symbol is easy - it's already there (23:19:30) xeno_by: parents are also easy (23:19:39) xeno_by: you just typecheck the things that come after "extends" (23:19:42) xeno_by: but decls are tricky (23:19:51) xeno_by: scalac goes through all the members of the class (23:20:03) xeno_by: and doesn't typecheck them... no, it doesn't (23:20:07) xeno_by: it just enters them (23:20:32) xeno_by: i.e. creates symbols for them and assigns lazy completers to those symbols so that if someone wants to know their signatures, they will go through the completers (23:20:34) xeno_by: and then (23:20:38) xeno_by: wait (23:20:40) xeno_by: there's one but (23:20:42) xeno_by: BUT (23:20:47) xeno_by: while we enter those symbols (23:20:53) xeno_by: our ClassInfoType is not ready yet (23:21:09) xeno_by: the class we're completing is still considered to be in the middle of being completing (23:21:12) xeno_by: so (23:21:24) xeno_by: when inside enterSym you try to ask that class for its super class (23:21:35) xeno_by: what happens is that check asks the class for its type signature (23:21:45) xeno_by: the ClassInfoType that consists of parents and decls (23:21:54) xeno_by: even though the parents are already calculated (23:22:01) xeno_by: the ClassInfoType as a whole is not (23:22:16) xeno_by: so scalac says that you're trying to complete something that's currently being completed (23:22:20) xeno_by: cyclic reference error (23:22:59) xeno_by: "cyclic" in English looks an awful lot like "суслик" in Russian (which means "gopher")
1 parent 527fd9a commit a90f39c

File tree

11 files changed

+44
-15
lines changed

11 files changed

+44
-15
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,7 @@ abstract class BCodeTypes extends BCodeIdiomatic {
835835
if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
836836
if (sym.isArtifact) ACC_SYNTHETIC else 0,
837837
if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
838+
if (sym.hasEnumFlag) ACC_ENUM else 0,
838839
if (sym.isVarargsMethod) ACC_VARARGS else 0,
839840
if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
840841
)

src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
244244
if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
245245
if (sym.isArtifact) ACC_SYNTHETIC else 0,
246246
if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
247+
if (sym.hasEnumFlag) ACC_ENUM else 0,
247248
if (sym.isVarargsMethod) ACC_VARARGS else 0,
248249
if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
249250
)

src/compiler/scala/tools/nsc/javac/JavaParsers.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
792792
val superclazz =
793793
AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType))
794794
addCompanionObject(consts ::: statics ::: predefs, atPos(pos) {
795-
ClassDef(mods, name, List(),
795+
ClassDef(mods | Flags.ENUM, name, List(),
796796
makeTemplate(superclazz :: interfaces, body))
797797
})
798798
}
@@ -811,10 +811,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
811811
skipAhead()
812812
accept(RBRACE)
813813
}
814-
// The STABLE flag is to signal to namer that this was read from a
815-
// java enum, and so should be given a Constant type (thereby making
816-
// it usable in annotations.)
817-
ValDef(Modifiers(Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr)
814+
ValDef(Modifiers(Flags.ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr)
818815
}
819816
}
820817

src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ abstract class ClassfileParser {
515515
val info = readType()
516516
val sym = ownerForFlags(jflags).newValue(name.toTermName, NoPosition, sflags)
517517

518-
// Note: the info may be overrwritten later with a generic signature
518+
// Note: the info may be overwritten later with a generic signature
519519
// parsed from SignatureATTR
520520
sym setInfo {
521521
if (jflags.isEnum) ConstantType(Constant(sym))

src/compiler/scala/tools/nsc/typechecker/Namers.scala

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,31 @@ trait Namers extends MethodSynthesis {
122122
|| (vd.mods.isPrivateLocal && !vd.mods.isCaseAccessor)
123123
|| (vd.name startsWith nme.OUTER)
124124
|| (context.unit.isJava)
125+
|| isEnumConstant(vd)
125126
)
127+
126128
def noFinishGetterSetter(vd: ValDef) = (
127129
(vd.mods.isPrivateLocal && !vd.mods.isLazy) // all lazy vals need accessors, even private[this]
128-
|| vd.symbol.isModuleVar)
130+
|| vd.symbol.isModuleVar
131+
|| isEnumConstant(vd))
132+
133+
/** Determines whether this field holds an enum constant.
134+
* To qualify, the following conditions must be met:
135+
* - The field's class has the ENUM flag set
136+
* - The field's class extends java.lang.Enum
137+
* - The field has the ENUM flag set
138+
* - The field is static
139+
* - The field is stable
140+
*/
141+
def isEnumConstant(vd: ValDef) = {
142+
val ownerHasEnumFlag =
143+
// Necessary to check because scalac puts Java's static members into the companion object
144+
// while Scala's enum constants live directly in the class.
145+
// We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal
146+
// cyclic reference error. See the commit message for details.
147+
if (context.unit.isJava) owner.companionClass.hasEnumFlag else owner.hasEnumFlag
148+
vd.mods.hasAllFlags(ENUM | STABLE | STATIC) && ownerHasEnumFlag
149+
}
129150

130151
def setPrivateWithin[T <: Symbol](tree: Tree, sym: T, mods: Modifiers): T =
131152
if (sym.isPrivateLocal || !mods.hasAccessBoundary) sym
@@ -609,11 +630,7 @@ trait Namers extends MethodSynthesis {
609630
else
610631
enterGetterSetter(tree)
611632

612-
// When java enums are read from bytecode, they are known to have
613-
// constant types by the jvm flag and assigned accordingly. When
614-
// they are read from source, the java parser marks them with the
615-
// STABLE flag, and now we receive that signal.
616-
if (tree.symbol hasAllFlags STABLE | JAVA)
633+
if (isEnumConstant(tree))
617634
tree.symbol setInfo ConstantType(Constant(tree.symbol))
618635
}
619636

src/reflect/scala/reflect/api/FlagSets.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ trait FlagSets { self: Universe =>
169169

170170
/** Flag indicating that tree was generated by the compiler */
171171
val SYNTHETIC: FlagSet
172+
173+
/** Flag indicating that tree represents an enum.
174+
*
175+
* It can only appear at
176+
* - the enum's class
177+
* - enum constants
178+
**/
179+
val ENUM: FlagSet
172180
}
173181

174182
/** The empty set of flags

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ trait Definitions extends api.StandardDefinitions {
363363
lazy val ComparableClass = requiredClass[java.lang.Comparable[_]] modifyInfo fixupAsAnyTrait
364364
lazy val JavaCloneableClass = requiredClass[java.lang.Cloneable]
365365
lazy val JavaNumberClass = requiredClass[java.lang.Number]
366+
lazy val JavaEnumClass = requiredClass[java.lang.Enum[_]]
366367
lazy val RemoteInterfaceClass = requiredClass[java.rmi.Remote]
367368
lazy val RemoteExceptionClass = requiredClass[java.rmi.RemoteException]
368369

src/reflect/scala/reflect/internal/FlagSets.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@ trait FlagSets extends api.FlagSets { self: SymbolTable =>
4343
val PRESUPER : FlagSet = Flags.PRESUPER
4444
val DEFAULTINIT : FlagSet = Flags.DEFAULTINIT
4545
val SYNTHETIC : FlagSet = Flags.SYNTHETIC
46+
val ENUM : FlagSet = Flags.ENUM
4647
}
4748
}

src/reflect/scala/reflect/internal/Flags.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import scala.collection.{ mutable, immutable }
6363
// 45: SYNCHRONIZED/M
6464
// 46: ARTIFACT
6565
// 47: DEFAULTMETHOD/M
66-
// 48:
66+
// 48: ENUM
6767
// 49:
6868
// 50:
6969
// 51: lateDEFERRED
@@ -119,6 +119,7 @@ class ModifierFlags {
119119
final val DEFAULTINIT = 1L << 41 // symbol is initialized to the default value: used by -Xcheckinit
120120
final val ARTIFACT = 1L << 46 // symbol should be ignored when typechecking; will be marked ACC_SYNTHETIC in bytecode
121121
final val DEFAULTMETHOD = 1L << 47 // symbol is a java default method
122+
final val ENUM = 1L << 48 // symbol is an enum
122123

123124
/** Symbols which are marked ARTIFACT. (Expand this list?)
124125
*
@@ -142,7 +143,7 @@ class ModifierFlags {
142143
}
143144
object ModifierFlags extends ModifierFlags
144145

145-
/** All flags and associated operatins */
146+
/** All flags and associated operations */
146147
class Flags extends ModifierFlags {
147148
final val METHOD = 1 << 6 // a method
148149
final val MODULE = 1 << 8 // symbol is module or class implementing a module
@@ -446,7 +447,7 @@ class Flags extends ModifierFlags {
446447
case SYNCHRONIZED => "<synchronized>" // (1L << 45)
447448
case ARTIFACT => "<artifact>" // (1L << 46)
448449
case DEFAULTMETHOD => "<defaultmethod>" // (1L << 47)
449-
case 0x1000000000000L => "" // (1L << 48)
450+
case ENUM => "<enum>" // (1L << 48)
450451
case 0x2000000000000L => "" // (1L << 49)
451452
case 0x4000000000000L => "" // (1L << 50)
452453
case `lateDEFERRED` => "<latedeferred>" // (1L << 51)

src/reflect/scala/reflect/internal/HasFlags.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ trait HasFlags {
8282
def hasAbstractFlag = hasFlag(ABSTRACT)
8383
def hasAccessorFlag = hasFlag(ACCESSOR)
8484
def hasDefault = hasFlag(DEFAULTPARAM) && hasFlag(METHOD | PARAM) // Second condition disambiguates with TRAIT
85+
def hasEnumFlag = hasFlag(ENUM)
8586
def hasLocalFlag = hasFlag(LOCAL)
8687
def hasModuleFlag = hasFlag(MODULE)
8788
def hasPackageFlag = hasFlag(PACKAGE)

0 commit comments

Comments
 (0)