diff --git a/java/com/google/turbine/binder/CompUnitPreprocessor.java b/java/com/google/turbine/binder/CompUnitPreprocessor.java index 98be8988..070bb154 100644 --- a/java/com/google/turbine/binder/CompUnitPreprocessor.java +++ b/java/com/google/turbine/binder/CompUnitPreprocessor.java @@ -117,7 +117,7 @@ public static PreprocessedCompUnit preprocess(CompUnit unit) { for (TyDecl decl : decls) { ClassSymbol sym = new ClassSymbol((!packageName.isEmpty() ? packageName + "/" : "") + decl.name()); - int access = access(decl.mods(), decl.tykind()); + int access = access(decl.mods(), decl); ImmutableMap children = preprocessChildren(unit.source(), types, sym, decl.members(), access); types.add(new SourceBoundClass(sym, null, children, access, decl)); @@ -167,12 +167,12 @@ private static ImmutableMap preprocessChildren( } /** Desugars access flags for a class. */ - public static int access(ImmutableSet mods, TurbineTyKind tykind) { + public static int access(ImmutableSet mods, TyDecl decl) { int access = 0; for (TurbineModifier m : mods) { access |= m.flag(); } - switch (tykind) { + switch (decl.tykind()) { case CLASS: access |= TurbineFlag.ACC_SUPER; break; @@ -180,11 +180,14 @@ public static int access(ImmutableSet mods, TurbineTyKind tykin access |= TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_INTERFACE; break; case ENUM: - // Assuming all enums are final is safe, because nothing outside - // the compilation unit can extend abstract enums anyways, and - // refactoring an existing enum to implement methods in the container - // class instead of the constants is not a breaking change. - access |= TurbineFlag.ACC_SUPER | TurbineFlag.ACC_ENUM | TurbineFlag.ACC_FINAL; + // Assuming all enums are non-abstract is safe, because nothing outside + // the compilation unit can extend abstract enums, and refactoring an + // existing enum to implement methods in the container class instead + // of the constants is not a breaking change. + access |= TurbineFlag.ACC_SUPER | TurbineFlag.ACC_ENUM; + if (isEnumFinal(decl.members())) { + access |= TurbineFlag.ACC_FINAL; + } break; case ANNOTATION: access |= TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION; @@ -196,9 +199,27 @@ public static int access(ImmutableSet mods, TurbineTyKind tykin return access; } + /** + * If any enum constants have a class body (which is recorded in the parser by setting ENUM_IMPL), + * the class generated for the enum needs to not have ACC_FINAL set. + */ + private static boolean isEnumFinal(ImmutableList declMembers) { + for (Tree t : declMembers) { + if (t.kind() != Tree.Kind.VAR_DECL) { + continue; + } + Tree.VarDecl var = (Tree.VarDecl) t; + if (!var.mods().contains(TurbineModifier.ENUM_IMPL)) { + continue; + } + return false; + } + return true; + } + /** Desugars access flags for an inner class. */ private static int innerClassAccess(int enclosing, TyDecl decl) { - int access = access(decl.mods(), decl.tykind()); + int access = access(decl.mods(), decl); // types declared in interfaces and annotations are implicitly public (JLS 9.5) if ((enclosing & (TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION)) != 0) { diff --git a/java/com/google/turbine/model/TurbineFlag.java b/java/com/google/turbine/model/TurbineFlag.java index 3e68a5e6..abc67707 100644 --- a/java/com/google/turbine/model/TurbineFlag.java +++ b/java/com/google/turbine/model/TurbineFlag.java @@ -52,6 +52,9 @@ public final class TurbineFlag { /** Default methods. */ public static final int ACC_DEFAULT = 1 << 16; + /** Enum constants with class bodies. */ + public static final int ACC_ENUM_IMPL = 1 << 17; + /** Synthetic constructors (e.g. of inner classes and enums). */ public static final int ACC_SYNTH_CTOR = 1 << 18; diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java index 3336ae8d..67c50241 100644 --- a/java/com/google/turbine/parse/Parser.java +++ b/java/com/google/turbine/parse/Parser.java @@ -554,15 +554,17 @@ private ImmutableList enumMembers(Ident enumName) { if (token == Token.LPAREN) { dropParens(); } + EnumSet access = EnumSet.copyOf(ENUM_CONSTANT_MODIFIERS); // TODO(cushon): consider desugaring enum constants later if (token == Token.LBRACE) { dropBlocks(); + access.add(TurbineModifier.ENUM_IMPL); } maybe(Token.COMMA); result.add( new VarDecl( position, - ENUM_CONSTANT_MODIFIERS, + access, annos.build(), new ClassTy( position, diff --git a/java/com/google/turbine/tree/Pretty.java b/java/com/google/turbine/tree/Pretty.java index 1062e6f4..96643bb5 100644 --- a/java/com/google/turbine/tree/Pretty.java +++ b/java/com/google/turbine/tree/Pretty.java @@ -556,6 +556,7 @@ private void printModifiers(ImmutableSet mods) { case ACC_SYNTHETIC: case ACC_BRIDGE: case COMPACT_CTOR: + case ENUM_IMPL: break; } } diff --git a/java/com/google/turbine/tree/TurbineModifier.java b/java/com/google/turbine/tree/TurbineModifier.java index 2bfe53e9..9400253c 100644 --- a/java/com/google/turbine/tree/TurbineModifier.java +++ b/java/com/google/turbine/tree/TurbineModifier.java @@ -48,7 +48,8 @@ public enum TurbineModifier { TRANSITIVE(TurbineFlag.ACC_TRANSITIVE), SEALED(TurbineFlag.ACC_SEALED), NON_SEALED(TurbineFlag.ACC_NON_SEALED), - COMPACT_CTOR(TurbineFlag.ACC_COMPACT_CTOR); + COMPACT_CTOR(TurbineFlag.ACC_COMPACT_CTOR), + ENUM_IMPL(TurbineFlag.ACC_ENUM_IMPL); private final int flag; diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java index c72fcd6d..47fb0436 100644 --- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java +++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java @@ -128,7 +128,7 @@ public static Map canonicalize(Map in) { for (ClassNode n : classes) { removeImplementation(n); removeUnusedInnerClassAttributes(infos, n); - makeEnumsFinal(all, n); + makeEnumsNonAbstract(all, n); sortAttributes(n); undeprecate(n); removePreviewVersion(n); @@ -187,17 +187,15 @@ private static boolean isDeprecated(List visibleAnnotations) { && visibleAnnotations.stream().anyMatch(a -> a.desc.equals("Ljava/lang/Deprecated;")); } - private static void makeEnumsFinal(Set all, ClassNode n) { + private static void makeEnumsNonAbstract(Set all, ClassNode n) { n.innerClasses.forEach( x -> { if (all.contains(x.name) && (x.access & Opcodes.ACC_ENUM) == Opcodes.ACC_ENUM) { x.access &= ~Opcodes.ACC_ABSTRACT; - x.access |= Opcodes.ACC_FINAL; } }); if ((n.access & Opcodes.ACC_ENUM) == Opcodes.ACC_ENUM) { n.access &= ~Opcodes.ACC_ABSTRACT; - n.access |= Opcodes.ACC_FINAL; } } diff --git a/javatests/com/google/turbine/lower/testdata/enum_abstract.test b/javatests/com/google/turbine/lower/testdata/enum_abstract.test index e7ef3a45..d318daff 100644 --- a/javatests/com/google/turbine/lower/testdata/enum_abstract.test +++ b/javatests/com/google/turbine/lower/testdata/enum_abstract.test @@ -11,3 +11,33 @@ class Test { }; } } +=== I.java === +interface I { + void f(); +} +=== EnumConstantImplementsInterface.java === +enum EnumConstantImplementsInterface implements I { + ONE { + @Override + public void f() {} + }; +} +=== EnumImplementsInterface.java === +enum EnumImplementsInterface implements I { + ONE; + + public void f() {} +} +=== EnumConstantImplementsMethod.java === +enum EnumConstantImplementsMethod { + ONE { + @Override + public void f() {} + }; + public void f() {} +} +=== EnumConstantEmptyBody.java === +enum EnumConstantEmptyBody { + ONE { + }; +} \ No newline at end of file