[fasta] Add support for annotations on enum values

Fixes #33083

Bug: https://github.com/dart-lang/sdk/issues/33083
Change-Id: If278ee3c59489944e2e821ae48ba3cb363dbbdd1
Reviewed-on: https://dart-review.googlesource.com/56496
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Peter von der Ahé <ahe@google.com>
Reviewed-by: Dan Rubel <danrubel@google.com>
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
index e1d7ba7..5f91fd5 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
@@ -128,17 +128,6 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeAnnotationOnEnumConstant = messageAnnotationOnEnumConstant;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageAnnotationOnEnumConstant = const MessageCode(
-    "AnnotationOnEnumConstant",
-    analyzerCode: "ANNOTATION_ON_ENUM_CONSTANT",
-    dart2jsCode: "*fatal*",
-    message: r"""Enum constants can't have annotations.""",
-    tip: r"""Try removing the annotation.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         int
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_enum_builder.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_enum_builder.dart
index 18cca9e..ef2a750 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_enum_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_enum_builder.dart
@@ -159,14 +159,15 @@
         charEndOffset);
     members["toString"] = toStringBuilder;
     String className = name;
-    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 3) {
-      String name = constantNamesAndOffsetsAndDocs[i];
-      int charOffset = constantNamesAndOffsetsAndDocs[i + 1];
-      String documentationComment = constantNamesAndOffsetsAndDocs[i + 2];
+    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 4) {
+      List<MetadataBuilder> metadata = constantNamesAndOffsetsAndDocs[i];
+      String name = constantNamesAndOffsetsAndDocs[i + 1];
+      int charOffset = constantNamesAndOffsetsAndDocs[i + 2];
+      String documentationComment = constantNamesAndOffsetsAndDocs[i + 3];
       if (members.containsKey(name)) {
         parent.addCompileTimeError(templateDuplicatedName.withArguments(name),
             charOffset, noLength, parent.fileUri);
-        constantNamesAndOffsetsAndDocs[i] = null;
+        constantNamesAndOffsetsAndDocs[i + 1] = null;
         continue;
       }
       if (name == className) {
@@ -175,11 +176,18 @@
             charOffset,
             noLength,
             parent.fileUri);
-        constantNamesAndOffsetsAndDocs[i] = null;
+        constantNamesAndOffsetsAndDocs[i + 1] = null;
         continue;
       }
-      KernelFieldBuilder fieldBuilder = new KernelFieldBuilder(null, selfType,
-          name, constMask | staticMask, parent, charOffset, null, true);
+      KernelFieldBuilder fieldBuilder = new KernelFieldBuilder(
+          metadata,
+          selfType,
+          name,
+          constMask | staticMask,
+          parent,
+          charOffset,
+          null,
+          true);
       metadataCollector?.setDocumentationComment(
           fieldBuilder.target, documentationComment);
       members[name] = fieldBuilder;
@@ -235,8 +243,8 @@
     toStringBuilder.body = new ReturnStatement(
         new DirectPropertyGet(new ThisExpression(), nameField));
     List<Expression> values = <Expression>[];
-    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 3) {
-      String name = constantNamesAndOffsetsAndDocs[i];
+    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 4) {
+      String name = constantNamesAndOffsetsAndDocs[i + 1];
       if (name != null) {
         KernelFieldBuilder builder = this[name];
         values.add(new StaticGet(builder.build(libraryBuilder)));
@@ -273,8 +281,8 @@
             ..parent = constructor);
     }
     int index = 0;
-    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 3) {
-      String constant = constantNamesAndOffsetsAndDocs[i];
+    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 4) {
+      String constant = constantNamesAndOffsetsAndDocs[i + 1];
       if (constant != null) {
         KernelFieldBuilder field = this[constant];
         field.build(libraryBuilder);
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 05f512f..08e0144 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1596,7 +1596,7 @@
 
   /// ```
   /// enumType:
-  ///   metadata 'enum' id '{' id [',' id]* [','] '}'
+  ///   metadata 'enum' id '{' metadata id [',' metadata id]* [','] '}'
   /// ;
   /// ```
   Token parseEnum(Token token) {
@@ -1618,10 +1618,6 @@
           break;
         }
         token = parseMetadataStar(token);
-        if (!identical(token.next, next)) {
-          listener.handleRecoverableError(
-              fasta.messageAnnotationOnEnumConstant, next, token);
-        }
         token = ensureIdentifier(token, IdentifierContext.enumValueDeclaration);
         next = token.next;
         count++;
diff --git a/pkg/front_end/lib/src/fasta/source/diet_listener.dart b/pkg/front_end/lib/src/fasta/source/diet_listener.dart
index 4adca66..58f6ebc 100644
--- a/pkg/front_end/lib/src/fasta/source/diet_listener.dart
+++ b/pkg/front_end/lib/src/fasta/source/diet_listener.dart
@@ -9,6 +9,7 @@
         AsyncMarker,
         Class,
         Expression,
+        Field,
         InterfaceType,
         Library,
         LibraryDependency,
@@ -33,8 +34,7 @@
 
 import '../kernel/kernel_body_builder.dart' show KernelBodyBuilder;
 
-import '../parser.dart'
-    show Assert, IdentifierContext, MemberKind, Parser, optional;
+import '../parser.dart' show Assert, MemberKind, Parser, optional;
 
 import '../problems.dart' show internalProblem, unexpected;
 
@@ -274,15 +274,6 @@
   }
 
   @override
-  void handleIdentifier(Token token, IdentifierContext context) {
-    if (context == IdentifierContext.enumValueDeclaration) {
-      // Discard the metadata.
-      pop();
-    }
-    super.handleIdentifier(token, context);
-  }
-
-  @override
   void handleQualified(Token period) {
     debugEvent("handleQualified");
     String suffix = pop();
@@ -643,11 +634,13 @@
   void endEnum(Token enumKeyword, Token leftBrace, int count) {
     debugEvent("Enum");
 
-    discard(count); // values
+    List metadataAndValues = new List.filled(count * 2, null, growable: true);
+    popList(count * 2, metadataAndValues);
+
     String name = pop();
     Token metadata = pop();
 
-    Builder enumBuilder = lookupBuilder(enumKeyword, null, name);
+    ClassBuilder enumBuilder = lookupBuilder(enumKeyword, null, name);
     Class target = enumBuilder.target;
     var metadataConstants = parseMetadata(enumBuilder, metadata);
     if (metadataConstants != null) {
@@ -655,6 +648,17 @@
         target.addAnnotation(metadataConstant);
       }
     }
+    for (int i = 0; i < metadataAndValues.length; i += 2) {
+      Token metadata = metadataAndValues[i];
+      String valueName = metadataAndValues[i + 1];
+      Builder builder = enumBuilder.scope.local[valueName];
+      if (metadata != null) {
+        Field field = builder.target;
+        for (var annotation in parseMetadata(builder, metadata)) {
+          field.addAnnotation(annotation);
+        }
+      }
+    }
 
     checkEmpty(enumKeyword.charOffset);
   }
diff --git a/pkg/front_end/lib/src/fasta/source/outline_builder.dart b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
index f2f3dfd..64f66b9 100644
--- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
@@ -276,8 +276,6 @@
   @override
   void handleIdentifier(Token token, IdentifierContext context) {
     if (context == IdentifierContext.enumValueDeclaration) {
-      // Discard the metadata.
-      pop();
       super.handleIdentifier(token, context);
       push(token.charOffset);
       String documentationComment = getDocumentationComment(token);
@@ -944,7 +942,7 @@
   void endEnum(Token enumKeyword, Token leftBrace, int count) {
     String documentationComment = getDocumentationComment(enumKeyword);
     List<Object> constantNamesAndOffsets = popList(
-        count * 3, new List<Object>.filled(count * 3, null, growable: true));
+        count * 4, new List<Object>.filled(count * 4, null, growable: true));
     int charOffset = pop();
     String name = pop();
     List<MetadataBuilder> metadata = pop();
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 1e8e061..440aab5 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -1940,12 +1940,6 @@
 ExpressionNotMetadata:
   template: "This can't be used as metadata; metadata should be a reference to a compile-time constant variable, or a call to a constant constructor."
 
-AnnotationOnEnumConstant:
-  template: "Enum constants can't have annotations."
-  tip: "Try removing the annotation."
-  analyzerCode: ANNOTATION_ON_ENUM_CONSTANT
-  dart2jsCode: "*fatal*"
-
 ExpectedAnInitializer:
   template: "Expected an initializer."
   analyzerCode: MISSING_INITIALIZER
diff --git a/pkg/front_end/testcases/annotation_on_enum_values.dart b/pkg/front_end/testcases/annotation_on_enum_values.dart
new file mode 100644
index 0000000..29dba81
--- /dev/null
+++ b/pkg/front_end/testcases/annotation_on_enum_values.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This test checks that annotations on enum values are preserved by the
+// compiler.
+
+const int hest = 42;
+
+class Fisk<T> {
+  final T x;
+  const Fisk.fisk(this.x);
+}
+
+enum Foo {
+  @hest bar,
+  @Fisk.fisk(hest) baz,
+  cafebabe,
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/annotation_on_enum_values.dart.direct.expect b/pkg/front_end/testcases/annotation_on_enum_values.dart.direct.expect
new file mode 100644
index 0000000..246f4cb
--- /dev/null
+++ b/pkg/front_end/testcases/annotation_on_enum_values.dart.direct.expect
@@ -0,0 +1,27 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Fisk<T extends core::Object = dynamic> extends core::Object {
+  final field self::Fisk::T x;
+  const constructor fisk(self::Fisk::T x) → void
+    : self::Fisk::x = x, super core::Object::•()
+    ;
+}
+class Foo extends core::Object {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::Foo> values = const <self::Foo>[self::Foo::bar, self::Foo::baz, self::Foo::cafebabe];
+  @self::hest
+  static const field self::Foo bar = const self::Foo::•(0, "Foo.bar");
+  @self::Fisk::fisk<dynamic>(self::hest)
+  static const field self::Foo baz = const self::Foo::•(1, "Foo.baz");
+  static const field self::Foo cafebabe = const self::Foo::•(2, "Foo.cafebabe");
+  const constructor •(core::int index, core::String _name) → void
+    : self::Foo::index = index, self::Foo::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{=self::Foo::_name};
+}
+static const field core::int hest = 42;
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/annotation_on_enum_values.dart.direct.transformed.expect b/pkg/front_end/testcases/annotation_on_enum_values.dart.direct.transformed.expect
new file mode 100644
index 0000000..246f4cb
--- /dev/null
+++ b/pkg/front_end/testcases/annotation_on_enum_values.dart.direct.transformed.expect
@@ -0,0 +1,27 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Fisk<T extends core::Object = dynamic> extends core::Object {
+  final field self::Fisk::T x;
+  const constructor fisk(self::Fisk::T x) → void
+    : self::Fisk::x = x, super core::Object::•()
+    ;
+}
+class Foo extends core::Object {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::Foo> values = const <self::Foo>[self::Foo::bar, self::Foo::baz, self::Foo::cafebabe];
+  @self::hest
+  static const field self::Foo bar = const self::Foo::•(0, "Foo.bar");
+  @self::Fisk::fisk<dynamic>(self::hest)
+  static const field self::Foo baz = const self::Foo::•(1, "Foo.baz");
+  static const field self::Foo cafebabe = const self::Foo::•(2, "Foo.cafebabe");
+  const constructor •(core::int index, core::String _name) → void
+    : self::Foo::index = index, self::Foo::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{=self::Foo::_name};
+}
+static const field core::int hest = 42;
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/annotation_on_enum_values.dart.outline.expect b/pkg/front_end/testcases/annotation_on_enum_values.dart.outline.expect
new file mode 100644
index 0000000..02c5c32
--- /dev/null
+++ b/pkg/front_end/testcases/annotation_on_enum_values.dart.outline.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Fisk<T extends core::Object = dynamic> extends core::Object {
+  final field self::Fisk::T x;
+  const constructor fisk(self::Fisk::T x) → void
+    ;
+}
+class Foo extends core::Object {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::Foo> values = const <self::Foo>[self::Foo::bar, self::Foo::baz, self::Foo::cafebabe];
+  static const field self::Foo bar = const self::Foo::•(0, "Foo.bar");
+  static const field self::Foo baz = const self::Foo::•(1, "Foo.baz");
+  static const field self::Foo cafebabe = const self::Foo::•(2, "Foo.cafebabe");
+  const constructor •(core::int index, core::String _name) → void
+    : self::Foo::index = index, self::Foo::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{=self::Foo::_name};
+}
+static const field core::int hest;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/annotation_on_enum_values.dart.strong.expect b/pkg/front_end/testcases/annotation_on_enum_values.dart.strong.expect
new file mode 100644
index 0000000..2c026ab
--- /dev/null
+++ b/pkg/front_end/testcases/annotation_on_enum_values.dart.strong.expect
@@ -0,0 +1,27 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Fisk<T extends core::Object = dynamic> extends core::Object {
+  final field self::Fisk::T x;
+  const constructor fisk(self::Fisk::T x) → void
+    : self::Fisk::x = x, super core::Object::•()
+    ;
+}
+class Foo extends core::Object {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::Foo> values = const <self::Foo>[self::Foo::bar, self::Foo::baz, self::Foo::cafebabe];
+  @self::hest
+  static const field self::Foo bar = const self::Foo::•(0, "Foo.bar");
+  @self::Fisk::fisk<core::int>(self::hest)
+  static const field self::Foo baz = const self::Foo::•(1, "Foo.baz");
+  static const field self::Foo cafebabe = const self::Foo::•(2, "Foo.cafebabe");
+  const constructor •(core::int index, core::String _name) → void
+    : self::Foo::index = index, self::Foo::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{=self::Foo::_name};
+}
+static const field core::int hest = 42;
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/annotation_on_enum_values.dart.strong.transformed.expect b/pkg/front_end/testcases/annotation_on_enum_values.dart.strong.transformed.expect
new file mode 100644
index 0000000..2c026ab
--- /dev/null
+++ b/pkg/front_end/testcases/annotation_on_enum_values.dart.strong.transformed.expect
@@ -0,0 +1,27 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Fisk<T extends core::Object = dynamic> extends core::Object {
+  final field self::Fisk::T x;
+  const constructor fisk(self::Fisk::T x) → void
+    : self::Fisk::x = x, super core::Object::•()
+    ;
+}
+class Foo extends core::Object {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::Foo> values = const <self::Foo>[self::Foo::bar, self::Foo::baz, self::Foo::cafebabe];
+  @self::hest
+  static const field self::Foo bar = const self::Foo::•(0, "Foo.bar");
+  @self::Fisk::fisk<core::int>(self::hest)
+  static const field self::Foo baz = const self::Foo::•(1, "Foo.baz");
+  static const field self::Foo cafebabe = const self::Foo::•(2, "Foo.cafebabe");
+  const constructor •(core::int index, core::String _name) → void
+    : self::Foo::index = index, self::Foo::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{=self::Foo::_name};
+}
+static const field core::int hest = 42;
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/ast_builder.status b/pkg/front_end/testcases/ast_builder.status
index 1301285..6e166ec 100644
--- a/pkg/front_end/testcases/ast_builder.status
+++ b/pkg/front_end/testcases/ast_builder.status
@@ -4,6 +4,7 @@
 
 # Status file for the ast_builder_test.dart test suite. This is testing
 # generating analyzer ASTs and connecting up type inference information.
+annotation_on_enum_values: Crash
 annotation_top: Crash
 argument_mismatch: Fail
 bug32629: Fail