Skip to content

fix #9324: forbid no-arg java.lang.Enum ctor #10027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
ModifierNotAllowedForDefinitionID,
CannotExtendJavaEnumID,
InvalidReferenceInImplicitNotFoundAnnotationID,
TraitMayNotDefineNativeMethodID
TraitMayNotDefineNativeMethodID,
JavaEnumParentArgsID

def errorNumber = ordinal - 2
}
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1528,6 +1528,12 @@ import transform.SymUtils._
def explain = ""
}

class JavaEnumParentArgs(parent: Type)(using Context)
extends TypeMsg(JavaEnumParentArgsID) {
def msg = em"""not enough arguments for constructor Enum: ${hl("(name: String, ordinal: Int)")}: ${hl(parent.show)}"""
def explain = ""
}

class CannotHaveSameNameAs(sym: Symbol, cls: Symbol, reason: CannotHaveSameNameAs.Reason)(using Context)
extends SyntaxMsg(CannotHaveSameNameAsID) {
import CannotHaveSameNameAs._
Expand Down
28 changes: 21 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ object RefChecks {
* and required classes. Also check that only `enum` constructs extend
* `java.lang.Enum`.
*/
private def checkParents(cls: Symbol)(using Context): Unit = cls.info match {
private def checkParents(cls: Symbol, parentTrees: List[Tree])(using Context): Unit = cls.info match {
case cinfo: ClassInfo =>
def checkSelfConforms(other: ClassSymbol, category: String, relation: String) = {
val otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType)
Expand All @@ -109,12 +109,26 @@ object RefChecks {
for (reqd <- cinfo.cls.givenSelfType.classSymbols)
checkSelfConforms(reqd, "missing requirement", "required")

def isClassExtendingJavaEnum =
!cls.isOneOf(Enum | Trait) && parents.exists(_.classSymbol == defn.JavaEnumClass)

// Prevent wrong `extends` of java.lang.Enum
if !migrateTo3 &&
!cls.isOneOf(Enum | Trait) &&
parents.exists(_.classSymbol == defn.JavaEnumClass)
then
report.error(CannotExtendJavaEnum(cls), cls.sourcePos)
if isClassExtendingJavaEnum then
if !migrateTo3 then // always error, only traits or enum-syntax is possible under scala 3.x
report.error(CannotExtendJavaEnum(cls), cls.sourcePos)
else
// conditionally error, we allow classes to extend java.lang.Enum in scala 2 migration mode,
// however the no-arg constructor is forbidden, we must look at the parent trees to see
// which overload is called.
val javaEnumCtor = defn.JavaEnumClass.primaryConstructor
parentTrees.exists {
case parent @ tpd.Apply(tpd.TypeApply(fn, _), _) if fn.tpe.termSymbol eq javaEnumCtor =>
// here we are simulating the error for missing arguments to a constructor.
report.error(JavaEnumParentArgs(parent.tpe), cls.sourcePos)
true
case _ =>
false
}

case _ =>
}
Expand Down Expand Up @@ -1089,7 +1103,7 @@ class RefChecks extends MiniPhase { thisPhase =>
override def transformTemplate(tree: Template)(using Context): Tree = try {
val cls = ctx.owner.asClass
checkOverloadedRestrictions(cls)
checkParents(cls)
checkParents(cls, tree.parents)
if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _))
checkCompanionNameClashes(cls)
checkAllOverrides(cls)
Expand Down
16 changes: 16 additions & 0 deletions tests/neg/extend-java-enum-migration.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- [E160] Type Error: tests/neg/extend-java-enum-migration.scala:9:12 --------------------------------------------------
9 |final class C extends jl.Enum[C] // error
| ^
| not enough arguments for constructor Enum: (name: String, ordinal: Int): Enum[C]
-- [E160] Type Error: tests/neg/extend-java-enum-migration.scala:11:7 --------------------------------------------------
11 |object O extends jl.Enum[O.type] // error
| ^
| not enough arguments for constructor Enum: (name: String, ordinal: Int): Enum[O.type]
-- [E160] Type Error: tests/neg/extend-java-enum-migration.scala:14:6 --------------------------------------------------
14 |class Sub extends T // error
| ^
| not enough arguments for constructor Enum: (name: String, ordinal: Int): Enum[T]
-- [E160] Type Error: tests/neg/extend-java-enum-migration.scala:17:10 -------------------------------------------------
17 |val foo = new java.lang.Enum[Color] {} // error
| ^
| not enough arguments for constructor Enum: (name: String, ordinal: Int): Enum[Color]
17 changes: 17 additions & 0 deletions tests/neg/extend-java-enum-migration.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import java.{lang => jl}

import language.`3.0-migration`

// This file is different from `tests/neg/extend-java-enum.scala` as we
// are testing that it is illegal to *not* pass arguments to jl.Enum
// in 3.0-migration

final class C extends jl.Enum[C] // error

object O extends jl.Enum[O.type] // error

trait T extends jl.Enum[T] // ok
class Sub extends T // error

abstract class Color(name: String, ordinal: Int) extends java.lang.Enum[Color](name, ordinal) // ok
val foo = new java.lang.Enum[Color] {} // error