Skip to content

make values and valueOf only available on enumerations #10002

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
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/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
24 changes: 19 additions & 5 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import printing.Formatting.hl
import ast.Trees._
import ast.untpd
import ast.tpd
import transform.SymUtils._

/** Messages
* ========
Expand Down Expand Up @@ -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"
}
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,3 @@ trait TypeAssigner {


object TypeAssigner extends TypeAssigner

61 changes: 61 additions & 0 deletions tests/neg/enum-values.check
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions tests/neg/enum-values.scala
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions tests/run/enum-custom-toString.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
14 changes: 2 additions & 12 deletions tests/run/enum-values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,31 +80,21 @@ 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]) =
val lookup = companion.valueOf(name)
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)
17 changes: 11 additions & 6 deletions tests/run/enums-java-compat.scala
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -37,4 +42,4 @@ object Test {
println("Correctly failed to retrieve illegal name, message: " + e.getMessage)
}
}
}
}
6 changes: 1 addition & 5 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ Schema => SemanticDB v4
Uri => Enums.scala
Text => empty
Language => Scala
Symbols => 185 entries
Symbols => 181 entries
Occurrences => 203 entries

Symbols:
Expand Down Expand Up @@ -697,7 +697,6 @@ _empty_/Enums.Maybe# => abstract sealed enum class Maybe
_empty_/Enums.Maybe#[A] => covariant typeparam A
_empty_/Enums.Maybe#`<init>`(). => primary ctor <init>
_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
Expand All @@ -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#`<init>`(). => primary ctor <init>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down