diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 7531b0f872fa..9a61c453a987 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -152,7 +152,8 @@ object DesugarEnums { } private def enumLookupMethods(constraints: EnumConstraints)(using Context): List[Tree] = - def scaffolding: List[Tree] = if constraints.cached then enumScaffolding(constraints.enumCases.map(_._2)) else Nil + def scaffolding: List[Tree] = + if constraints.isEnumeration then enumScaffolding(constraints.enumCases.map(_._2)) else Nil def valueCtor: List[Tree] = if constraints.requiresCreator then enumValueCreator :: Nil else Nil def fromOrdinal: Tree = def throwArg(ordinal: Tree) = diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 07864790acaf..bd6e01c85e14 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -23,6 +23,7 @@ import printing.Formatting.hl import ast.Trees._ import ast.untpd import ast.tpd +import transform.SymUtils._ /** Messages * ======== @@ -323,16 +324,29 @@ import ast.tpd .filter((d, n) => d <= maxDist && d < missing.length && d < n.length) .sorted // sort by distance first, alphabetically second + val enumClause = + if ((name eq nme.values) || (name eq nme.valueOf)) && site.classSymbol.companionClass.isEnumClass then + val kind = if name eq nme.values then i"${nme.values} array" else i"${nme.valueOf} lookup method" + // an assumption is made here that the values and valueOf methods were not generated + // because the enum defines non-singleton cases + i""" + |Although ${site.classSymbol.companionClass} is an enum, it has non-singleton cases, + |meaning a $kind is not defined""" + else + "" + + def prefixEnumClause(addendum: String) = + if enumClause.nonEmpty then s".$enumClause$addendum" else addendum + val finalAddendum = - if addendum.nonEmpty then addendum - else closest match { + if addendum.nonEmpty then prefixEnumClause(addendum) + else closest match case (d, n) :: _ => val siteName = site match case site: NamedType => site.name.show case site => i"$site" - s" - did you mean $siteName.$n?" - case Nil => "" - } + s" - did you mean $siteName.$n?$enumClause" + case Nil => prefixEnumClause("") ex"$selected $name is not a member of ${site.widen}$finalAddendum" } diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e614ff4dfab3..494aec369996 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -520,4 +520,3 @@ trait TypeAssigner { object TypeAssigner extends TypeAssigner - diff --git a/tests/neg/enum-values.check b/tests/neg/enum-values.check new file mode 100644 index 000000000000..c8632ed17e45 --- /dev/null +++ b/tests/neg/enum-values.check @@ -0,0 +1,61 @@ +-- [E008] Not Found Error: tests/neg/enum-values.scala:32:45 ----------------------------------------------------------- +32 | val tags: Array[Tag[?]] = Tag.values // error + | ^^^^^^^^^^ + | value values is not a member of object example.Tag. + | Although class Tag is an enum, it has non-singleton cases, + | meaning a values array is not defined. + | An extension method was tried, but could not be fully constructed: + | + | example.Extensions.extension_values(Tag) +-- [E008] Not Found Error: tests/neg/enum-values.scala:33:50 ----------------------------------------------------------- +33 | val listlikes: Array[ListLike[?]] = ListLike.values // error + | ^^^^^^^^^^^^^^^ + | value values is not a member of object example.ListLike. + | Although class ListLike is an enum, it has non-singleton cases, + | meaning a values array is not defined. + | An extension method was tried, but could not be fully constructed: + | + | example.Extensions.extension_values(ListLike) +-- [E008] Not Found Error: tests/neg/enum-values.scala:34:52 ----------------------------------------------------------- +34 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error + | ^^^^^^^^^^^^^^^^^ + | value values is not a member of object example.TypeCtorsK. + | Although class TypeCtorsK is an enum, it has non-singleton cases, + | meaning a values array is not defined. + | An extension method was tried, but could not be fully constructed: + | + | example.Extensions.extension_values(TypeCtorsK) +-- [E008] Not Found Error: tests/neg/enum-values.scala:36:6 ------------------------------------------------------------ +36 | Tag.valueOf("Int") // error + | ^^^^^^^^^^^ + | value valueOf is not a member of object example.Tag. + | Although class Tag is an enum, it has non-singleton cases, + | meaning a valueOf lookup method is not defined +-- [E008] Not Found Error: tests/neg/enum-values.scala:37:11 ----------------------------------------------------------- +37 | ListLike.valueOf("EmptyListLike") // error + | ^^^^^^^^^^^^^^^^ + | value valueOf is not a member of object example.ListLike - did you mean ListLike.valuef? + | Although class ListLike is an enum, it has non-singleton cases, + | meaning a valueOf lookup method is not defined +-- [E008] Not Found Error: tests/neg/enum-values.scala:38:13 ----------------------------------------------------------- +38 | TypeCtorsK.valueOf("Option") // error + | ^^^^^^^^^^^^^^^^^^ + | value valueOf is not a member of object example.TypeCtorsK. + | Although class TypeCtorsK is an enum, it has non-singleton cases, + | meaning a valueOf lookup method is not defined, but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import example.UnimportedExtensions.valueOf + | +-- [E008] Not Found Error: tests/neg/enum-values.scala:40:12 ----------------------------------------------------------- +40 | NotAnEnum.values // error + | ^^^^^^^^^^^^^^^^ + | value values is not a member of object example.NotAnEnum. + | An extension method was tried, but could not be fully constructed: + | + | example.Extensions.extension_values(NotAnEnum) +-- [E008] Not Found Error: tests/neg/enum-values.scala:41:12 ----------------------------------------------------------- +41 | NotAnEnum.valueOf("Foo") // error + | ^^^^^^^^^^^^^^^^^ + | value valueOf is not a member of object example.NotAnEnum diff --git a/tests/neg/enum-values.scala b/tests/neg/enum-values.scala new file mode 100644 index 000000000000..08069251af0e --- /dev/null +++ b/tests/neg/enum-values.scala @@ -0,0 +1,41 @@ +package example + +enum Tag[T]: + case Int extends Tag[Int] + case String extends Tag[String] + case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T] + +enum ListLike[+T]: + case Cons[T](head: T, tail: ListLike[T]) extends ListLike[T] + case EmptyListLike +object ListLike: + def valuef(s: String): ListLike[?] = ??? // this will usually trigger a "- did you mean ListLike.valuef" addendum + +object Extensions: + extension (foo: Nothing) // this will usually trigger an attempted extension method addendum + def values: Array[Tag[?]] = ??? + +enum TypeCtorsK[F[_]]: + case List extends TypeCtorsK[List] + case Option extends TypeCtorsK[Option] + case Const[T]() extends TypeCtorsK[[U] =>> T] + +object UnimportedExtensions: + extension (TypeCtorsKModule: TypeCtorsK.type) // this will usually trigger an import suggestions addendum + def valueOf(name: String): TypeCtorsK[?] = ??? + +object NotAnEnum // object without a companion class + +def Test: Unit = + import Tag._, ListLike._, TypeCtorsK._, Extensions._ + + val tags: Array[Tag[?]] = Tag.values // error + val listlikes: Array[ListLike[?]] = ListLike.values // error + val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error + + Tag.valueOf("Int") // error + ListLike.valueOf("EmptyListLike") // error + TypeCtorsK.valueOf("Option") // error + + NotAnEnum.values // error + NotAnEnum.valueOf("Foo") // error diff --git a/tests/run/enum-custom-toString.scala b/tests/run/enum-custom-toString.scala index 54605d388524..c2f3882931f9 100644 --- a/tests/run/enum-custom-toString.scala +++ b/tests/run/enum-custom-toString.scala @@ -59,17 +59,14 @@ object Tag: assert(EZ.E(0).productPrefix == "E", s"EZ.E(0).productPrefix = ${EZ.E(0).productPrefix}") assert(EC.F.toString == "F", s"EC.F.toString = ${EC.F.toString}") assert(EC.F.productPrefix == "F", s"EC.F.productPrefix = ${EC.F.productPrefix}") - assert(EC.valueOf("F") == EC.F, s"EC.valueOf(F) = ${EC.valueOf("F")}") assert(EC.G(0).toString == "G(0)", s"EC.G(0).toString = ${EC.G(0).toString}") assert(EC.G(0).productPrefix == "G", s"EC.G(0).productPrefix = ${EC.G(0).productPrefix}") assert(EO.H.toString == "overridden", s"EO.H.toString = ${EO.H.toString}") assert(EO.H.productPrefix == "noprefix", s"EO.H.productPrefix = ${EO.H.productPrefix}") - assert(EO.valueOf("H") == EO.H, s"EO.valueOf(H) = ${EO.valueOf("H")}") assert(EO.I(0).toString == "overridden", s"EO.I(0).toString = ${EO.I(0).toString}") assert(EO.I(0).productPrefix == "noprefix", s"EO.I(0).productPrefix = ${EO.I(0).productPrefix}") assert(EQ.J.toString == "overridden", s"EQ.J.toString = ${EQ.J.toString}") assert(EQ.J.productPrefix == "noprefix", s"EQ.J.productPrefix = ${EQ.J.productPrefix}") - assert(EQ.valueOf("J") == EQ.J, s"EQ.valueOf(J) = ${EQ.valueOf("J")}") assert(EQ.K(0).toString == "overridden", s"EQ.K(0).toString = ${EQ.K(0).toString}") assert(EQ.K(0).productPrefix == "noprefix", s"EQ.K(0).productPrefix = ${EQ.K(0).productPrefix}") assert(Tag.IntTag.productPrefix == "", s"Tag.IntTag.productPrefix = ${Tag.IntTag.productPrefix}") diff --git a/tests/run/enum-values.scala b/tests/run/enum-values.scala index 26a01b7133b5..40b5a4ebc7bd 100644 --- a/tests/run/enum-values.scala +++ b/tests/run/enum-values.scala @@ -80,22 +80,15 @@ enum ClassOnly: // this should still generate the `ordinal` and `fromOrdinal` co assert(summon[Mirror.SumOf[ClassOnly]].ordinal(BranchProd(1)) == 0) - val colors: Array[Color] = Color.values - val tags: Array[Tag[?]] = Tag.values - val exprs: Array[Expr[? >: Null]] = Expr.values - val listlikes: Array[ListLike[?]] = ListLike.values - val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values - + val colors: Array[Color] = Color.values + val exprs: Array[Expr[? >: Null]] = Expr.values val mixedParams: Array[MixedParams[?, ? <: [X, Y] =>> collection.Map[X, Y], ?]] = MixedParams.values def sameAs[T](arr: Array[T], compare: T*): Unit = assert(arr sameElements compare, s"${arr.show} does not correspond to ${compare.show}") sameAs(colors, Red, Green, Blue) - sameAs(tags, Int, String) sameAs(exprs, EmptyTree, AnyTree) - sameAs(listlikes, EmptyListLike) - sameAs(typeCtorsK, List, Option) sameAs(mixedParams, Foo) def singleton[E <: AnyRef](value: E, name: String, companion: ValueOf[E]) = @@ -103,8 +96,5 @@ enum ClassOnly: // this should still generate the `ordinal` and `fromOrdinal` co assert(value eq lookup, s"${value.show} is not identical to ${lookup.show}") singleton(Green, "Green", Color) - singleton(String, "String", Tag) singleton(AnyTree, "AnyTree", Expr) - singleton(EmptyListLike, "EmptyListLike", ListLike) - singleton(Option, "Option", TypeCtorsK) singleton(Foo, "Foo", MixedParams) diff --git a/tests/run/enums-java-compat.scala b/tests/run/enums-java-compat.scala index 592e38c8d603..0a67c676c4ce 100644 --- a/tests/run/enums-java-compat.scala +++ b/tests/run/enums-java-compat.scala @@ -1,14 +1,19 @@ -class JEnum { - def name: String = "Foo" - def action = "fofofo" +trait JEnum[E <: JEnum[E]] { self: reflect.Enum => + final def name: String = productPrefix } -enum A extends JEnum { +trait JEnumCompanion[E <: JEnum[E]] { + def valueOf(name: String): E + def values: Array[E] +} + +enum A extends JEnum[A] { case MONDAY, TUESDAY, SATURDAY case Stuff - case Someday(x: String) + // case Someday(x: String) // uncommenting this line will prevent `object A` from compiling def report = "Reported" } +object A extends JEnumCompanion[A] trait Foo1 trait Bar @@ -37,4 +42,4 @@ object Test { println("Correctly failed to retrieve illegal name, message: " + e.getMessage) } } -} \ No newline at end of file +} diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 3bbb646c55d4..90c39945db6f 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -641,7 +641,7 @@ Schema => SemanticDB v4 Uri => Enums.scala Text => empty Language => Scala -Symbols => 185 entries +Symbols => 181 entries Occurrences => 203 entries Symbols: @@ -697,7 +697,6 @@ _empty_/Enums.Maybe# => abstract sealed enum class Maybe _empty_/Enums.Maybe#[A] => covariant typeparam A _empty_/Enums.Maybe#``(). => primary ctor _empty_/Enums.Maybe. => final object Maybe -_empty_/Enums.Maybe.$values. => val method $values _empty_/Enums.Maybe.Just# => final case enum class Just _empty_/Enums.Maybe.Just#[A] => covariant typeparam A _empty_/Enums.Maybe.Just#_1(). => method _1 @@ -721,9 +720,6 @@ _empty_/Enums.Maybe.Just.unapply().[A] => typeparam A _empty_/Enums.Maybe.None. => case val static enum method None _empty_/Enums.Maybe.fromOrdinal(). => method fromOrdinal _empty_/Enums.Maybe.fromOrdinal().(ordinal) => param ordinal -_empty_/Enums.Maybe.valueOf(). => method valueOf -_empty_/Enums.Maybe.valueOf().($name) => param $name -_empty_/Enums.Maybe.values(). => method values _empty_/Enums.Planet# => abstract sealed enum class Planet _empty_/Enums.Planet#G. => final val method G _empty_/Enums.Planet#``(). => primary ctor diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala index be7dae4d7af6..28cc6ef1f17d 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala @@ -135,8 +135,6 @@ class EnumTestScala3: assert(Opt.Nn.ordinal == 1) assert(Opt.Sm(1).productPrefix == "Sm") assert(Opt.Nn.productPrefix == "Nn") - assert(Opt.valueOf("Nn") == Opt.Nn) - assert(Opt.values(0) == Opt.Nn) assert(Opt.Sm("hello").value == "hello") assert(encode(Opt.Sm(23)) == 23) assert(encode(Opt.Nn) == null)