Skip to content

Commit 8c56525

Browse files
authored
Merge pull request #10002 from dotty-staging/topic/enum-values-only-when-enumeration
make values and valueOf only available on enumerations
2 parents c0b527d + 32545a2 commit 8c56525

File tree

10 files changed

+137
-35
lines changed

10 files changed

+137
-35
lines changed

compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ object DesugarEnums {
152152
}
153153

154154
private def enumLookupMethods(constraints: EnumConstraints)(using Context): List[Tree] =
155-
def scaffolding: List[Tree] = if constraints.cached then enumScaffolding(constraints.enumCases.map(_._2)) else Nil
155+
def scaffolding: List[Tree] =
156+
if constraints.isEnumeration then enumScaffolding(constraints.enumCases.map(_._2)) else Nil
156157
def valueCtor: List[Tree] = if constraints.requiresCreator then enumValueCreator :: Nil else Nil
157158
def fromOrdinal: Tree =
158159
def throwArg(ordinal: Tree) =

compiler/src/dotty/tools/dotc/reporting/messages.scala

+19-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import printing.Formatting.hl
2323
import ast.Trees._
2424
import ast.untpd
2525
import ast.tpd
26+
import transform.SymUtils._
2627

2728
/** Messages
2829
* ========
@@ -323,16 +324,29 @@ import ast.tpd
323324
.filter((d, n) => d <= maxDist && d < missing.length && d < n.length)
324325
.sorted // sort by distance first, alphabetically second
325326

327+
val enumClause =
328+
if ((name eq nme.values) || (name eq nme.valueOf)) && site.classSymbol.companionClass.isEnumClass then
329+
val kind = if name eq nme.values then i"${nme.values} array" else i"${nme.valueOf} lookup method"
330+
// an assumption is made here that the values and valueOf methods were not generated
331+
// because the enum defines non-singleton cases
332+
i"""
333+
|Although ${site.classSymbol.companionClass} is an enum, it has non-singleton cases,
334+
|meaning a $kind is not defined"""
335+
else
336+
""
337+
338+
def prefixEnumClause(addendum: String) =
339+
if enumClause.nonEmpty then s".$enumClause$addendum" else addendum
340+
326341
val finalAddendum =
327-
if addendum.nonEmpty then addendum
328-
else closest match {
342+
if addendum.nonEmpty then prefixEnumClause(addendum)
343+
else closest match
329344
case (d, n) :: _ =>
330345
val siteName = site match
331346
case site: NamedType => site.name.show
332347
case site => i"$site"
333-
s" - did you mean $siteName.$n?"
334-
case Nil => ""
335-
}
348+
s" - did you mean $siteName.$n?$enumClause"
349+
case Nil => prefixEnumClause("")
336350

337351
ex"$selected $name is not a member of ${site.widen}$finalAddendum"
338352
}

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

-1
Original file line numberDiff line numberDiff line change
@@ -520,4 +520,3 @@ trait TypeAssigner {
520520

521521

522522
object TypeAssigner extends TypeAssigner
523-

tests/neg/enum-values.check

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
-- [E008] Not Found Error: tests/neg/enum-values.scala:32:45 -----------------------------------------------------------
2+
32 | val tags: Array[Tag[?]] = Tag.values // error
3+
| ^^^^^^^^^^
4+
| value values is not a member of object example.Tag.
5+
| Although class Tag is an enum, it has non-singleton cases,
6+
| meaning a values array is not defined.
7+
| An extension method was tried, but could not be fully constructed:
8+
|
9+
| example.Extensions.extension_values(Tag)
10+
-- [E008] Not Found Error: tests/neg/enum-values.scala:33:50 -----------------------------------------------------------
11+
33 | val listlikes: Array[ListLike[?]] = ListLike.values // error
12+
| ^^^^^^^^^^^^^^^
13+
| value values is not a member of object example.ListLike.
14+
| Although class ListLike is an enum, it has non-singleton cases,
15+
| meaning a values array is not defined.
16+
| An extension method was tried, but could not be fully constructed:
17+
|
18+
| example.Extensions.extension_values(ListLike)
19+
-- [E008] Not Found Error: tests/neg/enum-values.scala:34:52 -----------------------------------------------------------
20+
34 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error
21+
| ^^^^^^^^^^^^^^^^^
22+
| value values is not a member of object example.TypeCtorsK.
23+
| Although class TypeCtorsK is an enum, it has non-singleton cases,
24+
| meaning a values array is not defined.
25+
| An extension method was tried, but could not be fully constructed:
26+
|
27+
| example.Extensions.extension_values(TypeCtorsK)
28+
-- [E008] Not Found Error: tests/neg/enum-values.scala:36:6 ------------------------------------------------------------
29+
36 | Tag.valueOf("Int") // error
30+
| ^^^^^^^^^^^
31+
| value valueOf is not a member of object example.Tag.
32+
| Although class Tag is an enum, it has non-singleton cases,
33+
| meaning a valueOf lookup method is not defined
34+
-- [E008] Not Found Error: tests/neg/enum-values.scala:37:11 -----------------------------------------------------------
35+
37 | ListLike.valueOf("EmptyListLike") // error
36+
| ^^^^^^^^^^^^^^^^
37+
| value valueOf is not a member of object example.ListLike - did you mean ListLike.valuef?
38+
| Although class ListLike is an enum, it has non-singleton cases,
39+
| meaning a valueOf lookup method is not defined
40+
-- [E008] Not Found Error: tests/neg/enum-values.scala:38:13 -----------------------------------------------------------
41+
38 | TypeCtorsK.valueOf("Option") // error
42+
| ^^^^^^^^^^^^^^^^^^
43+
| value valueOf is not a member of object example.TypeCtorsK.
44+
| Although class TypeCtorsK is an enum, it has non-singleton cases,
45+
| meaning a valueOf lookup method is not defined, but could be made available as an extension method.
46+
|
47+
| The following import might fix the problem:
48+
|
49+
| import example.UnimportedExtensions.valueOf
50+
|
51+
-- [E008] Not Found Error: tests/neg/enum-values.scala:40:12 -----------------------------------------------------------
52+
40 | NotAnEnum.values // error
53+
| ^^^^^^^^^^^^^^^^
54+
| value values is not a member of object example.NotAnEnum.
55+
| An extension method was tried, but could not be fully constructed:
56+
|
57+
| example.Extensions.extension_values(NotAnEnum)
58+
-- [E008] Not Found Error: tests/neg/enum-values.scala:41:12 -----------------------------------------------------------
59+
41 | NotAnEnum.valueOf("Foo") // error
60+
| ^^^^^^^^^^^^^^^^^
61+
| value valueOf is not a member of object example.NotAnEnum

tests/neg/enum-values.scala

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package example
2+
3+
enum Tag[T]:
4+
case Int extends Tag[Int]
5+
case String extends Tag[String]
6+
case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T]
7+
8+
enum ListLike[+T]:
9+
case Cons[T](head: T, tail: ListLike[T]) extends ListLike[T]
10+
case EmptyListLike
11+
object ListLike:
12+
def valuef(s: String): ListLike[?] = ??? // this will usually trigger a "- did you mean ListLike.valuef" addendum
13+
14+
object Extensions:
15+
extension (foo: Nothing) // this will usually trigger an attempted extension method addendum
16+
def values: Array[Tag[?]] = ???
17+
18+
enum TypeCtorsK[F[_]]:
19+
case List extends TypeCtorsK[List]
20+
case Option extends TypeCtorsK[Option]
21+
case Const[T]() extends TypeCtorsK[[U] =>> T]
22+
23+
object UnimportedExtensions:
24+
extension (TypeCtorsKModule: TypeCtorsK.type) // this will usually trigger an import suggestions addendum
25+
def valueOf(name: String): TypeCtorsK[?] = ???
26+
27+
object NotAnEnum // object without a companion class
28+
29+
def Test: Unit =
30+
import Tag._, ListLike._, TypeCtorsK._, Extensions._
31+
32+
val tags: Array[Tag[?]] = Tag.values // error
33+
val listlikes: Array[ListLike[?]] = ListLike.values // error
34+
val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error
35+
36+
Tag.valueOf("Int") // error
37+
ListLike.valueOf("EmptyListLike") // error
38+
TypeCtorsK.valueOf("Option") // error
39+
40+
NotAnEnum.values // error
41+
NotAnEnum.valueOf("Foo") // error

tests/run/enum-custom-toString.scala

-3
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,14 @@ object Tag:
5959
assert(EZ.E(0).productPrefix == "E", s"EZ.E(0).productPrefix = ${EZ.E(0).productPrefix}")
6060
assert(EC.F.toString == "F", s"EC.F.toString = ${EC.F.toString}")
6161
assert(EC.F.productPrefix == "F", s"EC.F.productPrefix = ${EC.F.productPrefix}")
62-
assert(EC.valueOf("F") == EC.F, s"EC.valueOf(F) = ${EC.valueOf("F")}")
6362
assert(EC.G(0).toString == "G(0)", s"EC.G(0).toString = ${EC.G(0).toString}")
6463
assert(EC.G(0).productPrefix == "G", s"EC.G(0).productPrefix = ${EC.G(0).productPrefix}")
6564
assert(EO.H.toString == "overridden", s"EO.H.toString = ${EO.H.toString}")
6665
assert(EO.H.productPrefix == "noprefix", s"EO.H.productPrefix = ${EO.H.productPrefix}")
67-
assert(EO.valueOf("H") == EO.H, s"EO.valueOf(H) = ${EO.valueOf("H")}")
6866
assert(EO.I(0).toString == "overridden", s"EO.I(0).toString = ${EO.I(0).toString}")
6967
assert(EO.I(0).productPrefix == "noprefix", s"EO.I(0).productPrefix = ${EO.I(0).productPrefix}")
7068
assert(EQ.J.toString == "overridden", s"EQ.J.toString = ${EQ.J.toString}")
7169
assert(EQ.J.productPrefix == "noprefix", s"EQ.J.productPrefix = ${EQ.J.productPrefix}")
72-
assert(EQ.valueOf("J") == EQ.J, s"EQ.valueOf(J) = ${EQ.valueOf("J")}")
7370
assert(EQ.K(0).toString == "overridden", s"EQ.K(0).toString = ${EQ.K(0).toString}")
7471
assert(EQ.K(0).productPrefix == "noprefix", s"EQ.K(0).productPrefix = ${EQ.K(0).productPrefix}")
7572
assert(Tag.IntTag.productPrefix == "", s"Tag.IntTag.productPrefix = ${Tag.IntTag.productPrefix}")

tests/run/enum-values.scala

+2-12
Original file line numberDiff line numberDiff line change
@@ -80,31 +80,21 @@ enum ClassOnly: // this should still generate the `ordinal` and `fromOrdinal` co
8080

8181
assert(summon[Mirror.SumOf[ClassOnly]].ordinal(BranchProd(1)) == 0)
8282

83-
val colors: Array[Color] = Color.values
84-
val tags: Array[Tag[?]] = Tag.values
85-
val exprs: Array[Expr[? >: Null]] = Expr.values
86-
val listlikes: Array[ListLike[?]] = ListLike.values
87-
val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values
88-
83+
val colors: Array[Color] = Color.values
84+
val exprs: Array[Expr[? >: Null]] = Expr.values
8985
val mixedParams: Array[MixedParams[?, ? <: [X, Y] =>> collection.Map[X, Y], ?]] = MixedParams.values
9086

9187
def sameAs[T](arr: Array[T], compare: T*): Unit =
9288
assert(arr sameElements compare, s"${arr.show} does not correspond to ${compare.show}")
9389

9490
sameAs(colors, Red, Green, Blue)
95-
sameAs(tags, Int, String)
9691
sameAs(exprs, EmptyTree, AnyTree)
97-
sameAs(listlikes, EmptyListLike)
98-
sameAs(typeCtorsK, List, Option)
9992
sameAs(mixedParams, Foo)
10093

10194
def singleton[E <: AnyRef](value: E, name: String, companion: ValueOf[E]) =
10295
val lookup = companion.valueOf(name)
10396
assert(value eq lookup, s"${value.show} is not identical to ${lookup.show}")
10497

10598
singleton(Green, "Green", Color)
106-
singleton(String, "String", Tag)
10799
singleton(AnyTree, "AnyTree", Expr)
108-
singleton(EmptyListLike, "EmptyListLike", ListLike)
109-
singleton(Option, "Option", TypeCtorsK)
110100
singleton(Foo, "Foo", MixedParams)

tests/run/enums-java-compat.scala

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
class JEnum {
2-
def name: String = "Foo"
3-
def action = "fofofo"
1+
trait JEnum[E <: JEnum[E]] { self: reflect.Enum =>
2+
final def name: String = productPrefix
43
}
54

6-
enum A extends JEnum {
5+
trait JEnumCompanion[E <: JEnum[E]] {
6+
def valueOf(name: String): E
7+
def values: Array[E]
8+
}
9+
10+
enum A extends JEnum[A] {
711
case MONDAY, TUESDAY, SATURDAY
812
case Stuff
9-
case Someday(x: String)
13+
// case Someday(x: String) // uncommenting this line will prevent `object A` from compiling
1014
def report = "Reported"
1115
}
16+
object A extends JEnumCompanion[A]
1217

1318
trait Foo1
1419
trait Bar
@@ -37,4 +42,4 @@ object Test {
3742
println("Correctly failed to retrieve illegal name, message: " + e.getMessage)
3843
}
3944
}
40-
}
45+
}

tests/semanticdb/metac.expect

+1-5
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ Schema => SemanticDB v4
641641
Uri => Enums.scala
642642
Text => empty
643643
Language => Scala
644-
Symbols => 185 entries
644+
Symbols => 181 entries
645645
Occurrences => 203 entries
646646

647647
Symbols:
@@ -697,7 +697,6 @@ _empty_/Enums.Maybe# => abstract sealed enum class Maybe
697697
_empty_/Enums.Maybe#[A] => covariant typeparam A
698698
_empty_/Enums.Maybe#`<init>`(). => primary ctor <init>
699699
_empty_/Enums.Maybe. => final object Maybe
700-
_empty_/Enums.Maybe.$values. => val method $values
701700
_empty_/Enums.Maybe.Just# => final case enum class Just
702701
_empty_/Enums.Maybe.Just#[A] => covariant typeparam A
703702
_empty_/Enums.Maybe.Just#_1(). => method _1
@@ -721,9 +720,6 @@ _empty_/Enums.Maybe.Just.unapply().[A] => typeparam A
721720
_empty_/Enums.Maybe.None. => case val static enum method None
722721
_empty_/Enums.Maybe.fromOrdinal(). => method fromOrdinal
723722
_empty_/Enums.Maybe.fromOrdinal().(ordinal) => param ordinal
724-
_empty_/Enums.Maybe.valueOf(). => method valueOf
725-
_empty_/Enums.Maybe.valueOf().($name) => param $name
726-
_empty_/Enums.Maybe.values(). => method values
727723
_empty_/Enums.Planet# => abstract sealed enum class Planet
728724
_empty_/Enums.Planet#G. => final val method G
729725
_empty_/Enums.Planet#`<init>`(). => primary ctor <init>

tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala

-2
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,6 @@ class EnumTestScala3:
135135
assert(Opt.Nn.ordinal == 1)
136136
assert(Opt.Sm(1).productPrefix == "Sm")
137137
assert(Opt.Nn.productPrefix == "Nn")
138-
assert(Opt.valueOf("Nn") == Opt.Nn)
139-
assert(Opt.values(0) == Opt.Nn)
140138
assert(Opt.Sm("hello").value == "hello")
141139
assert(encode(Opt.Sm(23)) == 23)
142140
assert(encode(Opt.Nn) == null)

0 commit comments

Comments
 (0)