From bc507ca732b9c3652a08e0397aa4feb69c12475f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 14 Oct 2020 15:28:41 +0200 Subject: [PATCH 1/4] make values and valueOf only available on enumerations --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 2 +- tests/neg/enum-values.scala | 25 +++++++++++++++++++ tests/run/enum-custom-toString.scala | 3 --- tests/run/enum-values.scala | 14 ++--------- tests/run/enums-java-compat.scala | 17 ++++++++----- tests/semanticdb/metac.expect | 6 +---- .../testsuite/compiler/EnumTestScala3.scala | 2 -- 7 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 tests/neg/enum-values.scala diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 7531b0f872fa..a12989c48f82 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -152,7 +152,7 @@ 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/tests/neg/enum-values.scala b/tests/neg/enum-values.scala new file mode 100644 index 000000000000..f3297c1d6360 --- /dev/null +++ b/tests/neg/enum-values.scala @@ -0,0 +1,25 @@ +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 + +enum TypeCtorsK[F[_]]: + case List extends TypeCtorsK[List] + case Option extends TypeCtorsK[Option] + case Const[T]() extends TypeCtorsK[[U] =>> T] + +def Test: Unit = + import Tag._, ListLike._, TypeCtorsK._ + import reflect.Selectable.reflectiveSelectable + + val tags: Array[Tag[?]] = Tag.values // error: value values is not a member of object Tag + val listlikes: Array[ListLike[?]] = ListLike.values // error: value values is not a member of object ListLike + val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error: value values is not a member of object TypeCtorsK + + Tag.valueOf("Int") // error: value valueOf is not a member of object Tag + ListLike.valueOf("EmptyListLike") // error: value valueOf is not a member of object ListLike + TypeCtorsK.valueOf("Option") // error: value valueOf is not a member of object TypeCtorsK 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) From ca143966453951a681cc573f901381be8c755b32 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 14 Oct 2020 17:18:58 +0200 Subject: [PATCH 2/4] stub valueOf and values for non-enumeration --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 25 ++++++++++++++++++- tests/neg/enum-values.check | 24 ++++++++++++++++++ tests/neg/enum-values.scala | 12 ++++----- tests/semanticdb/metac.expect | 8 +++++- 4 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 tests/neg/enum-values.check diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index a12989c48f82..25777e07cdfd 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -151,8 +151,31 @@ object DesugarEnums { valueOfDef :: Nil } + /** This generates the same public API as `enumScaffolding`, but stubs implementations to fail at compiletime and + * not generate bytecode + */ + private def enumScaffoldingStubbed(using Context): List[Tree] = { + val rawEnumClassRef = rawRef(enumClass.typeRef) + extension (tpe: NamedType) def ofRawEnum = AppliedTypeTree(ref(tpe), rawEnumClassRef) + + def stub(description: String) = Apply(ref(defn.Compiletime_error), Literal(Constant( + i"Although $enumClass is an enum, it has non-singleton cases, which prevents it from having a $description"))) + + val valuesDef = DefDef(nme.values, Nil, Nil, defn.ArrayType.ofRawEnum, stub(i"${nme.values} array")) + .withFlags(Synthetic | Inline) + + val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil), + TypeTree(), stub(i"${nme.valueOf} lookup method")) + .withFlags(Synthetic | Inline) + + valuesDef :: + valueOfDef :: Nil + } + private def enumLookupMethods(constraints: EnumConstraints)(using Context): List[Tree] = - def scaffolding: List[Tree] = if constraints.isEnumeration then enumScaffolding(constraints.enumCases.map(_._2)) else Nil + def scaffolding: List[Tree] = + if constraints.isEnumeration then enumScaffolding(constraints.enumCases.map(_._2)) + else enumScaffoldingStubbed def valueCtor: List[Tree] = if constraints.requiresCreator then enumValueCreator :: Nil else Nil def fromOrdinal: Tree = def throwArg(ordinal: Tree) = diff --git a/tests/neg/enum-values.check b/tests/neg/enum-values.check new file mode 100644 index 000000000000..0998919a8d2c --- /dev/null +++ b/tests/neg/enum-values.check @@ -0,0 +1,24 @@ +-- Error: tests/neg/enum-values.scala:19:45 ---------------------------------------------------------------------------- +19 | val tags: Array[Tag[?]] = Tag.values // error + | ^^^^^^^^^^ + | Although class Tag is an enum, it has non-singleton cases, which prevents it from having a values array +-- Error: tests/neg/enum-values.scala:20:50 ---------------------------------------------------------------------------- +20 | val listlikes: Array[ListLike[?]] = ListLike.values // error + | ^^^^^^^^^^^^^^^ + | Although class ListLike is an enum, it has non-singleton cases, which prevents it from having a values array +-- Error: tests/neg/enum-values.scala:21:52 ---------------------------------------------------------------------------- +21 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error + | ^^^^^^^^^^^^^^^^^ + | Although class TypeCtorsK is an enum, it has non-singleton cases, which prevents it from having a values array +-- Error: tests/neg/enum-values.scala:23:13 ---------------------------------------------------------------------------- +23 | Tag.valueOf("Int") // error + | ^^^^^^^^^^^^^^^^^^ + |Although class Tag is an enum, it has non-singleton cases, which prevents it from having a valueOf lookup method +-- Error: tests/neg/enum-values.scala:24:18 ---------------------------------------------------------------------------- +24 | ListLike.valueOf("EmptyListLike") // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |Although class ListLike is an enum, it has non-singleton cases, which prevents it from having a valueOf lookup method +-- Error: tests/neg/enum-values.scala:25:20 ---------------------------------------------------------------------------- +25 | TypeCtorsK.valueOf("Option") // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |Although class TypeCtorsK is an enum, it has non-singleton cases, which prevents it from having a valueOf lookup method diff --git a/tests/neg/enum-values.scala b/tests/neg/enum-values.scala index f3297c1d6360..fd81276ae0e0 100644 --- a/tests/neg/enum-values.scala +++ b/tests/neg/enum-values.scala @@ -16,10 +16,10 @@ def Test: Unit = import Tag._, ListLike._, TypeCtorsK._ import reflect.Selectable.reflectiveSelectable - val tags: Array[Tag[?]] = Tag.values // error: value values is not a member of object Tag - val listlikes: Array[ListLike[?]] = ListLike.values // error: value values is not a member of object ListLike - val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error: value values is not a member of object TypeCtorsK + 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: value valueOf is not a member of object Tag - ListLike.valueOf("EmptyListLike") // error: value valueOf is not a member of object ListLike - TypeCtorsK.valueOf("Option") // error: value valueOf is not a member of object TypeCtorsK + Tag.valueOf("Int") // error + ListLike.valueOf("EmptyListLike") // error + TypeCtorsK.valueOf("Option") // error diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 90c39945db6f..09bb70b72c71 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 => 181 entries +Symbols => 187 entries Occurrences => 203 entries Symbols: @@ -720,6 +720,9 @@ _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(). => macro valueOf +_empty_/Enums.Maybe.valueOf().($name) => param $name +_empty_/Enums.Maybe.values(). => macro values _empty_/Enums.Planet# => abstract sealed enum class Planet _empty_/Enums.Planet#G. => final val method G _empty_/Enums.Planet#``(). => primary ctor @@ -819,6 +822,9 @@ _empty_/Enums.`<:<`.fromOrdinal(). => method fromOrdinal _empty_/Enums.`<:<`.fromOrdinal().(ordinal) => param ordinal _empty_/Enums.`<:<`.given_T(). => final implicit method given_T _empty_/Enums.`<:<`.given_T().[T] => typeparam T +_empty_/Enums.`<:<`.valueOf(). => macro valueOf +_empty_/Enums.`<:<`.valueOf().($name) => param $name +_empty_/Enums.`<:<`.values(). => macro values _empty_/Enums.extension_unwrap(). => method extension_unwrap _empty_/Enums.extension_unwrap().(ev) => implicit param ev _empty_/Enums.extension_unwrap().(opt) => param opt From 58e084eacfa0eaf0c81c929721c0b87effd2d395 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Thu, 15 Oct 2020 13:40:54 +0200 Subject: [PATCH 3/4] alternative using select error addendum --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 24 +------- .../tools/dotc/typer/ErrorReporting.scala | 13 +++- .../dotty/tools/dotc/typer/TypeAssigner.scala | 3 +- tests/neg/enum-values.check | 60 ++++++++++++------- tests/neg/enum-values.scala | 14 ++++- tests/semanticdb/metac.expect | 8 +-- 6 files changed, 67 insertions(+), 55 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 25777e07cdfd..9a61c453a987 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -151,31 +151,9 @@ object DesugarEnums { valueOfDef :: Nil } - /** This generates the same public API as `enumScaffolding`, but stubs implementations to fail at compiletime and - * not generate bytecode - */ - private def enumScaffoldingStubbed(using Context): List[Tree] = { - val rawEnumClassRef = rawRef(enumClass.typeRef) - extension (tpe: NamedType) def ofRawEnum = AppliedTypeTree(ref(tpe), rawEnumClassRef) - - def stub(description: String) = Apply(ref(defn.Compiletime_error), Literal(Constant( - i"Although $enumClass is an enum, it has non-singleton cases, which prevents it from having a $description"))) - - val valuesDef = DefDef(nme.values, Nil, Nil, defn.ArrayType.ofRawEnum, stub(i"${nme.values} array")) - .withFlags(Synthetic | Inline) - - val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil), - TypeTree(), stub(i"${nme.valueOf} lookup method")) - .withFlags(Synthetic | Inline) - - valuesDef :: - valueOfDef :: Nil - } - private def enumLookupMethods(constraints: EnumConstraints)(using Context): List[Tree] = def scaffolding: List[Tree] = - if constraints.isEnumeration then enumScaffolding(constraints.enumCases.map(_._2)) - else enumScaffoldingStubbed + 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/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 03c5b4dd32cf..92cddb480ef5 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -4,13 +4,15 @@ package typer import ast._ import core._ -import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ +import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._, Names._ import Implicits._, Flags._, Constants.Constant import util.Spans._ import util.SrcPos import config.Feature import java.util.regex.Matcher.quoteReplacement import reporting._ +import transform.SymUtils._ +import StdNames._ object ErrorReporting { @@ -145,7 +147,7 @@ object ErrorReporting { else "" def selectErrorAddendum - (tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String) + (tree: untpd.RefTree, qual1: Tree, qualType: Type, name: Name, suggestImports: Type => String) (using Context): String = val attempts: List[Tree] = qual1.getAttachment(Typer.HiddenSearchFailure) match case Some(failures) => @@ -171,6 +173,13 @@ object ErrorReporting { |or drop any spaces behind the operator.""" else if qualType.isBottomType then "" + else if ((name eq nme.values) || (name eq nme.valueOf)) && qualType.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 ${qualType.classSymbol.companionClass} is an enum, it has non-singleton cases, + |which prevents it from having a $kind.""" else val add = suggestImports( ViewProto(qualType.widen, diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e614ff4dfab3..14d275677db5 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -163,7 +163,7 @@ trait TypeAssigner { errorType(ex"$qualType does not have a constructor", tree.srcPos) else { val kind = if (name.isTypeName) "type" else "value" - def addendum = err.selectErrorAddendum(tree, qual1, qualType, importSuggestionAddendum) + def addendum = err.selectErrorAddendum(tree, qual1, qualType, name, importSuggestionAddendum) errorType(NotAMember(qualType, name, kind, addendum), tree.srcPos) } } @@ -520,4 +520,3 @@ trait TypeAssigner { object TypeAssigner extends TypeAssigner - diff --git a/tests/neg/enum-values.check b/tests/neg/enum-values.check index 0998919a8d2c..64e1fa0f8c83 100644 --- a/tests/neg/enum-values.check +++ b/tests/neg/enum-values.check @@ -1,24 +1,44 @@ --- Error: tests/neg/enum-values.scala:19:45 ---------------------------------------------------------------------------- -19 | val tags: Array[Tag[?]] = Tag.values // error +-- [E008] Not Found Error: tests/neg/enum-values.scala:28:45 ----------------------------------------------------------- +28 | val tags: Array[Tag[?]] = Tag.values // error | ^^^^^^^^^^ - | Although class Tag is an enum, it has non-singleton cases, which prevents it from having a values array --- Error: tests/neg/enum-values.scala:20:50 ---------------------------------------------------------------------------- -20 | val listlikes: Array[ListLike[?]] = ListLike.values // error + | value values is not a member of object example.Tag. + | Although class Tag is an enum, it has non-singleton cases, + | which prevents it from having a values array. +-- [E008] Not Found Error: tests/neg/enum-values.scala:29:50 ----------------------------------------------------------- +29 | val listlikes: Array[ListLike[?]] = ListLike.values // error | ^^^^^^^^^^^^^^^ - | Although class ListLike is an enum, it has non-singleton cases, which prevents it from having a values array --- Error: tests/neg/enum-values.scala:21:52 ---------------------------------------------------------------------------- -21 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error + | value values is not a member of object example.ListLike. + | Although class ListLike is an enum, it has non-singleton cases, + | which prevents it from having a values array. +-- [E008] Not Found Error: tests/neg/enum-values.scala:30:52 ----------------------------------------------------------- +30 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error | ^^^^^^^^^^^^^^^^^ - | Although class TypeCtorsK is an enum, it has non-singleton cases, which prevents it from having a values array --- Error: tests/neg/enum-values.scala:23:13 ---------------------------------------------------------------------------- -23 | Tag.valueOf("Int") // error + | value values is not a member of object example.TypeCtorsK. + | Although class TypeCtorsK is an enum, it has non-singleton cases, + | which prevents it from having a values array. +-- [E008] Not Found Error: tests/neg/enum-values.scala:32:6 ------------------------------------------------------------ +32 | 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, + | which prevents it from having a valueOf lookup method. +-- [E008] Not Found Error: tests/neg/enum-values.scala:33:11 ----------------------------------------------------------- +33 | ListLike.valueOf("EmptyListLike") // error + | ^^^^^^^^^^^^^^^^ + | value valueOf is not a member of object example.ListLike. + | Although class ListLike is an enum, it has non-singleton cases, + | which prevents it from having a valueOf lookup method. +-- [E008] Not Found Error: tests/neg/enum-values.scala:34:13 ----------------------------------------------------------- +34 | TypeCtorsK.valueOf("Option") // error | ^^^^^^^^^^^^^^^^^^ - |Although class Tag is an enum, it has non-singleton cases, which prevents it from having a valueOf lookup method --- Error: tests/neg/enum-values.scala:24:18 ---------------------------------------------------------------------------- -24 | ListLike.valueOf("EmptyListLike") // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Although class ListLike is an enum, it has non-singleton cases, which prevents it from having a valueOf lookup method --- Error: tests/neg/enum-values.scala:25:20 ---------------------------------------------------------------------------- -25 | TypeCtorsK.valueOf("Option") // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Although class TypeCtorsK is an enum, it has non-singleton cases, which prevents it from having a valueOf lookup method + | value valueOf is not a member of object example.TypeCtorsK. + | Although class TypeCtorsK is an enum, it has non-singleton cases, + | which prevents it from having a valueOf lookup method. +-- [E008] Not Found Error: tests/neg/enum-values.scala:36:12 ----------------------------------------------------------- +36 | NotAnEnum.values // error + | ^^^^^^^^^^^^^^^^ + | value values is not a member of object example.NotAnEnum +-- [E008] Not Found Error: tests/neg/enum-values.scala:37:12 ----------------------------------------------------------- +37 | 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 index fd81276ae0e0..136036efdb37 100644 --- a/tests/neg/enum-values.scala +++ b/tests/neg/enum-values.scala @@ -1,3 +1,5 @@ +package example + enum Tag[T]: case Int extends Tag[Int] case String extends Tag[String] @@ -6,15 +8,22 @@ enum 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 (TagModule: Tag.type) // this will usually trigger an import suggestions 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 NotAnEnum // object without a companion class + def Test: Unit = import Tag._, ListLike._, TypeCtorsK._ - import reflect.Selectable.reflectiveSelectable val tags: Array[Tag[?]] = Tag.values // error val listlikes: Array[ListLike[?]] = ListLike.values // error @@ -23,3 +32,6 @@ def Test: Unit = Tag.valueOf("Int") // error ListLike.valueOf("EmptyListLike") // error TypeCtorsK.valueOf("Option") // error + + NotAnEnum.values // error + NotAnEnum.valueOf("Foo") // error diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 09bb70b72c71..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 => 187 entries +Symbols => 181 entries Occurrences => 203 entries Symbols: @@ -720,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(). => macro valueOf -_empty_/Enums.Maybe.valueOf().($name) => param $name -_empty_/Enums.Maybe.values(). => macro values _empty_/Enums.Planet# => abstract sealed enum class Planet _empty_/Enums.Planet#G. => final val method G _empty_/Enums.Planet#``(). => primary ctor @@ -822,9 +819,6 @@ _empty_/Enums.`<:<`.fromOrdinal(). => method fromOrdinal _empty_/Enums.`<:<`.fromOrdinal().(ordinal) => param ordinal _empty_/Enums.`<:<`.given_T(). => final implicit method given_T _empty_/Enums.`<:<`.given_T().[T] => typeparam T -_empty_/Enums.`<:<`.valueOf(). => macro valueOf -_empty_/Enums.`<:<`.valueOf().($name) => param $name -_empty_/Enums.`<:<`.values(). => macro values _empty_/Enums.extension_unwrap(). => method extension_unwrap _empty_/Enums.extension_unwrap().(ev) => implicit param ev _empty_/Enums.extension_unwrap().(opt) => param opt From 32545a24cd607ccc9e78e443bac1119c4b9ee728 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Thu, 15 Oct 2020 14:34:58 +0200 Subject: [PATCH 4/4] merge enum clause with other addendums --- .../dotty/tools/dotc/reporting/messages.scala | 24 +++++-- .../tools/dotc/typer/ErrorReporting.scala | 13 +--- .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- tests/neg/enum-values.check | 65 ++++++++++++------- tests/neg/enum-values.scala | 8 ++- 5 files changed, 69 insertions(+), 43 deletions(-) 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/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 92cddb480ef5..03c5b4dd32cf 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -4,15 +4,13 @@ package typer import ast._ import core._ -import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._, Names._ +import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ import Implicits._, Flags._, Constants.Constant import util.Spans._ import util.SrcPos import config.Feature import java.util.regex.Matcher.quoteReplacement import reporting._ -import transform.SymUtils._ -import StdNames._ object ErrorReporting { @@ -147,7 +145,7 @@ object ErrorReporting { else "" def selectErrorAddendum - (tree: untpd.RefTree, qual1: Tree, qualType: Type, name: Name, suggestImports: Type => String) + (tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String) (using Context): String = val attempts: List[Tree] = qual1.getAttachment(Typer.HiddenSearchFailure) match case Some(failures) => @@ -173,13 +171,6 @@ object ErrorReporting { |or drop any spaces behind the operator.""" else if qualType.isBottomType then "" - else if ((name eq nme.values) || (name eq nme.valueOf)) && qualType.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 ${qualType.classSymbol.companionClass} is an enum, it has non-singleton cases, - |which prevents it from having a $kind.""" else val add = suggestImports( ViewProto(qualType.widen, diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 14d275677db5..494aec369996 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -163,7 +163,7 @@ trait TypeAssigner { errorType(ex"$qualType does not have a constructor", tree.srcPos) else { val kind = if (name.isTypeName) "type" else "value" - def addendum = err.selectErrorAddendum(tree, qual1, qualType, name, importSuggestionAddendum) + def addendum = err.selectErrorAddendum(tree, qual1, qualType, importSuggestionAddendum) errorType(NotAMember(qualType, name, kind, addendum), tree.srcPos) } } diff --git a/tests/neg/enum-values.check b/tests/neg/enum-values.check index 64e1fa0f8c83..c8632ed17e45 100644 --- a/tests/neg/enum-values.check +++ b/tests/neg/enum-values.check @@ -1,44 +1,61 @@ --- [E008] Not Found Error: tests/neg/enum-values.scala:28:45 ----------------------------------------------------------- -28 | val tags: Array[Tag[?]] = Tag.values // error +-- [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, - | which prevents it from having a values array. --- [E008] Not Found Error: tests/neg/enum-values.scala:29:50 ----------------------------------------------------------- -29 | val listlikes: Array[ListLike[?]] = ListLike.values // error + | 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, - | which prevents it from having a values array. --- [E008] Not Found Error: tests/neg/enum-values.scala:30:52 ----------------------------------------------------------- -30 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error + | 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, - | which prevents it from having a values array. --- [E008] Not Found Error: tests/neg/enum-values.scala:32:6 ------------------------------------------------------------ -32 | Tag.valueOf("Int") // error + | 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, - | which prevents it from having a valueOf lookup method. --- [E008] Not Found Error: tests/neg/enum-values.scala:33:11 ----------------------------------------------------------- -33 | ListLike.valueOf("EmptyListLike") // error + | 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. + | 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, - | which prevents it from having a valueOf lookup method. --- [E008] Not Found Error: tests/neg/enum-values.scala:34:13 ----------------------------------------------------------- -34 | TypeCtorsK.valueOf("Option") // error + | 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, - | which prevents it from having a valueOf lookup method. --- [E008] Not Found Error: tests/neg/enum-values.scala:36:12 ----------------------------------------------------------- -36 | NotAnEnum.values // error + | 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 --- [E008] Not Found Error: tests/neg/enum-values.scala:37:12 ----------------------------------------------------------- -37 | NotAnEnum.valueOf("Foo") // 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 index 136036efdb37..08069251af0e 100644 --- a/tests/neg/enum-values.scala +++ b/tests/neg/enum-values.scala @@ -12,7 +12,7 @@ object ListLike: def valuef(s: String): ListLike[?] = ??? // this will usually trigger a "- did you mean ListLike.valuef" addendum object Extensions: - extension (TagModule: Tag.type) // this will usually trigger an import suggestions addendum + extension (foo: Nothing) // this will usually trigger an attempted extension method addendum def values: Array[Tag[?]] = ??? enum TypeCtorsK[F[_]]: @@ -20,10 +20,14 @@ enum TypeCtorsK[F[_]]: 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._ + import Tag._, ListLike._, TypeCtorsK._, Extensions._ val tags: Array[Tag[?]] = Tag.values // error val listlikes: Array[ListLike[?]] = ListLike.values // error