Skip to content

Commit 0842f23

Browse files
authored
Merge pull request scala#10424 from som-snytt/issue/12800-enum-exhaustivity
Avoid brittle flags encoding for Java enums
2 parents 6b16203 + 11db53c commit 0842f23

File tree

21 files changed

+107
-98
lines changed

21 files changed

+107
-98
lines changed

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

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -305,24 +305,16 @@ abstract class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
305305
def javaClassfileFlags(classSym: Symbol): Int = {
306306
assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
307307
import asm.Opcodes._
308-
def enumFlags = ACC_ENUM | {
309-
// Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
310-
// We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
311-
// Java enums for exhaustiveness checking.
312-
val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
313-
if (hasAbstractMethod) ACC_ABSTRACT else 0
314-
}
315-
// scala/bug#9393: the classfile / java source parser make java annotation symbols look like classes.
316-
// here we recover the actual classfile flags.
317-
( if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0) |
318-
( if (classSym.isPublic) ACC_PUBLIC else 0) |
319-
( if (classSym.isFinal) ACC_FINAL else 0) |
308+
// scala/bug#9393: the classfile / java source parser make java annotation symbols look like classes.
309+
// here we recover the actual classfile flags.
310+
(if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0) |
311+
(if (classSym.isPublic) ACC_PUBLIC else 0) |
312+
(if (classSym.isFinal) ACC_FINAL else 0) |
320313
// see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.)
321-
( if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER) |
322-
// for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags))
323-
( if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0) |
324-
( if (classSym.isArtifact) ACC_SYNTHETIC else 0) |
325-
( if (classSym.hasJavaEnumFlag) enumFlags else 0)
314+
(if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER) |
315+
(if (classSym.hasAbstractFlag) ACC_ABSTRACT else 0) |
316+
(if (classSym.isArtifact) ACC_SYNTHETIC else 0) |
317+
(if (classSym.hasJavaEnumFlag) ACC_ENUM else 0)
326318
}
327319

328320
// Check for hasAnnotationFlag for scala/bug#9393: the classfile / java source parsers add

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,11 +1030,14 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
10301030
accept(RBRACE)
10311031
val superclazz =
10321032
AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType))
1033+
val hasAbstractMember = body.exists {
1034+
case m: MemberDef => m.mods.hasFlag(Flags.DEFERRED)
1035+
case _ => false
1036+
}
10331037
val finalFlag = if (enumIsFinal) Flags.FINAL else 0L
1038+
val abstractFlag = if (hasAbstractMember) Flags.ABSTRACT else 0L
10341039
addCompanionObject(consts ::: statics ::: predefs, atPos(pos) {
1035-
// Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also ClassfileParser.
1036-
// This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags.
1037-
ClassDef(mods | Flags.JAVA_ENUM | Flags.SEALED | Flags.ABSTRACT | finalFlag, name, List(),
1040+
ClassDef(mods | Flags.JAVA_ENUM | Flags.SEALED | abstractFlag | finalFlag, name, List(),
10381041
makeTemplate(superclazz :: interfaces, body))
10391042
})
10401043
}

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

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -593,19 +593,13 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
593593
getScope(jflags) enter sym
594594

595595
// sealed java enums
596-
if (jflags.isEnum) {
597-
val enumClass = sym.owner.linkedClassOfClass
598-
enumClass match {
596+
if (jflags.isEnum)
597+
sym.owner.linkedClassOfClass match {
599598
case NoSymbol =>
600599
devWarning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.")
601-
case linked =>
602-
if (!linked.isSealed)
603-
// Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also JavaParsers.
604-
// This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags.
605-
linked setFlag (SEALED | ABSTRACT)
606-
linked addChild sym
600+
case enumClass =>
601+
enumClass.addChild(sym)
607602
}
608-
}
609603
}
610604
}
611605

@@ -1382,9 +1376,11 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
13821376
ClassInfoType(superTpe1 :: ifacesTypes, instanceScope, clazz)
13831377
}
13841378
sym.setInfo(info)
1385-
for (k <- permittedSubclasses)
1386-
if (k.parentSymbols.contains(sym))
1387-
sym.addChild(k)
1379+
// enum children are its enum fields, so don't register subclasses (which are listed as permitted)
1380+
if (!sym.hasJavaEnumFlag)
1381+
for (k <- permittedSubclasses)
1382+
if (k.parentSymbols.contains(sym))
1383+
sym.addChild(k)
13881384
}
13891385
}
13901386

src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ trait TreeAndTypeAnalysis extends Debugging {
129129
def filterAndSortChildren(children: Set[Symbol]) = {
130130
// symbols which are both sealed and abstract need not be covered themselves,
131131
// because all of their children must be and they cannot otherwise be created.
132-
val children1 = children.toList.filterNot(child => child.isSealed && child.isAbstractClass).sortBy(_.sealedSortName)
132+
val children1 = children.toList
133+
.filterNot(child => child.isSealed && (child.isAbstractClass || child.hasJavaEnumFlag))
134+
.sortBy(_.sealedSortName)
133135
children1.filterNot { child =>
134136
// remove private abstract children that are superclasses of other children, for example in t6159 drop X2
135137
child.isPrivate && child.isAbstractClass && children1.exists(sym => (sym ne child) && sym.isSubClass(child))
@@ -874,7 +876,7 @@ trait MatchAnalysis extends MatchApproximation {
874876
case args => args
875877
}.map(ListExample)
876878
case _ if isTupleSymbol(cls) => args(brevity = true).map(TupleExample)
877-
case _ if cls.isSealed && cls.isAbstractClass =>
879+
case _ if cls.isSealed && (cls.isAbstractClass || cls.hasJavaEnumFlag) =>
878880
// don't report sealed abstract classes, since
879881
// 1) they can't be instantiated
880882
// 2) we are already reporting any missing subclass (since we know the full domain)

src/reflect/scala/reflect/internal/ClassfileConstants.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ object ClassfileConstants {
353353
case JAVA_ACC_STATIC => STATIC
354354
case JAVA_ACC_ABSTRACT => if (isClass) ABSTRACT else DEFERRED
355355
case JAVA_ACC_INTERFACE => TRAIT | INTERFACE | ABSTRACT
356-
case JAVA_ACC_ENUM => JAVA_ENUM
356+
case JAVA_ACC_ENUM => if (isClass) JAVA_ENUM | SEALED else JAVA_ENUM
357357
case JAVA_ACC_ANNOTATION => JAVA_ANNOTATION
358358
case _ => 0L
359359
}

test/files/neg/sealed-java-enums.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// scalac: -Xfatal-warnings
1+
// scalac: -Werror
22
//
33
import java.lang.Thread.State
44
import java.lang.Thread.State._

test/files/neg/t12800.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
matcher_1.scala:8: warning: match may not be exhaustive.
2+
It would fail on the following input: ORANGE
3+
jb match {
4+
^
5+
error: No warnings can be incurred under -Werror.
6+
1 warning
7+
1 error

test/files/neg/t12800/JetBrains.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
public enum JetBrains {
3+
APPLE {
4+
@Override public String text() {
5+
return "Cupertino tech company";
6+
}
7+
},
8+
ORANGE
9+
;
10+
public String text() {
11+
return "Boring default";
12+
}
13+
}

test/files/neg/t12800/matcher_1.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
// scalac: -Werror -Xsource:3
3+
4+
import JetBrains.*
5+
6+
class C {
7+
def f(jb: JetBrains): Int =
8+
jb match {
9+
case APPLE => 42
10+
}
11+
}

test/files/neg/t8700b-new.check

Lines changed: 0 additions & 11 deletions
This file was deleted.

test/files/neg/t8700b-new/Bar_2.scala

Lines changed: 0 additions & 11 deletions
This file was deleted.

test/files/neg/t8700b-new/Baz_1.java

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/files/neg/t8700b-new/Foo_1.java

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/files/neg/t8700b-old/Bar_2.scala

Lines changed: 0 additions & 11 deletions
This file was deleted.

test/files/neg/t8700b-old/Foo_1.java

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Bar_2.scala:4: warning: match may not be exhaustive.
22
It would fail on the following input: B
3-
def bar1(foo: Foo_1) = foo match {
4-
^
3+
def bar1(foo: Foo) = foo match {
4+
^
55
Bar_2.scala:8: warning: match may not be exhaustive.
66
It would fail on the following input: B
7-
def bar2(foo: Baz_1) = foo match {
8-
^
7+
def bar2(foo: Baz) = foo match {
8+
^
99
error: No warnings can be incurred under -Werror.
1010
2 warnings
1111
1 error

test/files/neg/t8700b/Bar_2.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// scalac: -Werror
2+
//
3+
object Bar {
4+
def bar1(foo: Foo) = foo match {
5+
case Foo.A => 1
6+
}
7+
8+
def bar2(foo: Baz) = foo match {
9+
case Baz.A => 1
10+
}
11+
}

test/files/neg/t8700b-old/Baz_1.java renamed to test/files/neg/t8700b/Baz.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// javaVersion: 8
2-
public enum Baz_1 {
1+
public enum Baz {
32
A {
43
public void baz1() {}
54
},

test/files/neg/t8700b/Foo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public enum Foo {
2+
A,
3+
B
4+
}

test/files/pos/t12800/JetBrains.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
public enum JetBrains {
3+
APPLE {
4+
@Override public String text() {
5+
return "Cupertino tech company";
6+
}
7+
},
8+
ORANGE {
9+
@Override public String text() {
10+
return "SoCal county";
11+
}
12+
};
13+
public abstract String text();
14+
}

test/files/pos/t12800/matcher_1.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
// scalac: -Werror -Xsource:3
3+
4+
import JetBrains.*
5+
6+
class C {
7+
def f(jb: JetBrains): Int =
8+
jb match {
9+
case APPLE => 42
10+
case ORANGE => 27
11+
}
12+
}

0 commit comments

Comments
 (0)