Report duplicate set entries with a different error

It will take some time until the CFE will take on reporting this error (and
duplicate keys in maps).

For now: detect whether a map is used as the implementation of a set literal,
and if so report the error differently.

Change-Id: I9f657189c79a4532023f615aa91bf63602f27664
Reviewed-on: https://dart-review.googlesource.com/c/91102
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 9913a66..124cfce 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -61,6 +61,9 @@
   /// The `Map` class defined in 'dart:core';
   ClassEntity get mapClass;
 
+  /// The `Set` class defined in 'dart:core';
+  ClassEntity get unmodifiableSetClass;
+
   /// The `Iterable` class defined in 'dart:core';
   ClassEntity get iterableClass;
 
@@ -653,6 +656,11 @@
   ClassEntity _mapClass;
   ClassEntity get mapClass => _mapClass ??= _findClass(coreLibrary, 'Map');
 
+  /// The `_UnmodifiableSet` class defined in 'dart:collection';
+  ClassEntity _unmodifiableSetClass;
+  ClassEntity get unmodifiableSetClass => _unmodifiableSetClass ??=
+      _findClass(_env.lookupLibrary(Uris.dart_collection), '_UnmodifiableSet');
+
   /// The `Iterable` class defined in 'dart:core';
   ClassEntity _iterableClass;
   ClassEntity get iterableClass =>
diff --git a/pkg/compiler/lib/src/constants/evaluation.dart b/pkg/compiler/lib/src/constants/evaluation.dart
index ff9355f..386a2ee 100644
--- a/pkg/compiler/lib/src/constants/evaluation.dart
+++ b/pkg/compiler/lib/src/constants/evaluation.dart
@@ -24,6 +24,12 @@
   /// Type in the enclosing constructed
   InterfaceType get enclosingConstructedType;
 
+  /// Whether the immediate parent is a set literal.
+  ///
+  /// Used to distinguish map-literal from set-literal errors. This will be
+  /// removed once the CFE reports errors on constants.
+  bool get immediateUnderSetLiteral;
+
   /// Read environments string passed in using the '-Dname=value' option.
   String readFromEnvironment(String name);
 
@@ -55,6 +61,8 @@
   ConstantValue evaluateConstructor(ConstructorEntity constructor,
       InterfaceType type, ConstantValue evaluate());
 
+  ConstantValue evaluateMapBody(ConstantValue evaluate());
+
   ConstantValue evaluateField(FieldEntity field, ConstantValue evaluate());
 
   /// `true` if assertions are enabled.
@@ -70,6 +78,7 @@
 abstract class EvaluationEnvironmentBase implements EvaluationEnvironment {
   Link<Spannable> _spannableStack = const Link<Spannable>();
   InterfaceType enclosingConstructedType;
+  bool immediateUnderSetLiteral = false;
   final Set<FieldEntity> _currentlyEvaluatedFields = new Set<FieldEntity>();
   final bool constantRequired;
 
@@ -117,13 +126,26 @@
     _spannableStack = _spannableStack.prepend(constructor);
     var old = enclosingConstructedType;
     enclosingConstructedType = type;
+    if (type.element == commonElements.unmodifiableSetClass) {
+      immediateUnderSetLiteral = true;
+    }
     ConstantValue result = evaluate();
+    // All const set literals have as an immediate child a const map. The map
+    // evaluate method calls evaluateMapBody and reset this flag immediately.
+    // Because there are no other children, the flag is kept false.
+    assert(!immediateUnderSetLiteral);
     enclosingConstructedType = old;
     _spannableStack = _spannableStack.tail;
     return result;
   }
 
   @override
+  ConstantValue evaluateMapBody(ConstantValue evaluate()) {
+    immediateUnderSetLiteral = false;
+    return evaluate();
+  }
+
+  @override
   void reportError(
       ConstantExpression expression, MessageKind kind, Map arguments) {
     if (constantRequired) {
diff --git a/pkg/compiler/lib/src/constants/expressions.dart b/pkg/compiler/lib/src/constants/expressions.dart
index 60a18ca..82f640d 100644
--- a/pkg/compiler/lib/src/constants/expressions.dart
+++ b/pkg/compiler/lib/src/constants/expressions.dart
@@ -433,23 +433,32 @@
   @override
   ConstantValue evaluate(
       EvaluationEnvironment environment, ConstantSystem constantSystem) {
-    Map<ConstantValue, ConstantValue> map = <ConstantValue, ConstantValue>{};
-    for (int i = 0; i < keys.length; i++) {
-      ConstantValue key = keys[i].evaluate(environment, constantSystem);
-      if (!key.isConstant) {
-        return new NonConstantValue();
+    // TODO(sigmund): delete once the CFE provides these error messages.
+    bool isSetLiteral = environment.immediateUnderSetLiteral;
+    return environment.evaluateMapBody(() {
+      Map<ConstantValue, ConstantValue> map = <ConstantValue, ConstantValue>{};
+      for (int i = 0; i < keys.length; i++) {
+        ConstantValue key = keys[i].evaluate(environment, constantSystem);
+        if (!key.isConstant) {
+          return new NonConstantValue();
+        }
+        ConstantValue value = values[i].evaluate(environment, constantSystem);
+        if (!value.isConstant) {
+          return new NonConstantValue();
+        }
+        if (map.containsKey(key)) {
+          environment.reportError(
+              keys[i],
+              isSetLiteral
+                  ? MessageKind.EQUAL_SET_ENTRY
+                  : MessageKind.EQUAL_MAP_ENTRY_KEY,
+              {});
+        }
+        map[key] = value;
       }
-      ConstantValue value = values[i].evaluate(environment, constantSystem);
-      if (!value.isConstant) {
-        return new NonConstantValue();
-      }
-      if (map.containsKey(key)) {
-        environment.reportError(keys[i], MessageKind.EQUAL_MAP_ENTRY_KEY, {});
-      }
-      map[key] = value;
-    }
-    return constantSystem.createMap(environment.commonElements, type,
-        map.keys.toList(), map.values.toList());
+      return constantSystem.createMap(environment.commonElements, type,
+          map.keys.toList(), map.values.toList());
+    });
   }
 
   ConstantExpression apply(NormalizedArguments arguments) {
diff --git a/pkg/compiler/lib/src/diagnostics/messages.dart b/pkg/compiler/lib/src/diagnostics/messages.dart
index c8131bb..a3532c9 100644
--- a/pkg/compiler/lib/src/diagnostics/messages.dart
+++ b/pkg/compiler/lib/src/diagnostics/messages.dart
@@ -37,6 +37,7 @@
   CYCLIC_COMPILE_TIME_CONSTANTS,
   DIRECTLY_THROWING_NSM,
   EQUAL_MAP_ENTRY_KEY,
+  EQUAL_SET_ENTRY,
   EXTRANEOUS_MODIFIER,
   EXTRANEOUS_MODIFIER_REPLACE,
   FORIN_NOT_ASSIGNABLE,
@@ -433,6 +434,16 @@
 }"""
           ]),
 
+      MessageKind.EQUAL_SET_ENTRY: const MessageTemplate(
+          MessageKind.EQUAL_SET_ENTRY, "An entry appears twice in the set.",
+          howToFix: "Try removing one of the entries.",
+          examples: const [
+            """
+main() {
+  var m = const {'foo', 'bar', 'foo'};
+}"""
+          ]),
+
       MessageKind.COMPILER_CRASHED: const MessageTemplate(
           MessageKind.COMPILER_CRASHED,
           "The compiler crashed when compiling this element."),
diff --git a/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart b/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart
index 3b16a86..c35cadf 100644
--- a/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart
+++ b/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart
@@ -67,6 +67,8 @@
 
   bool get checkCasts => true;
 
+  bool get immediateUnderSetLiteral => _environment.immediateUnderSetLiteral;
+
   @override
   String readFromEnvironment(String name) => env[name];
 
@@ -130,6 +132,11 @@
   }
 
   @override
+  ConstantValue evaluateMapBody(ConstantValue evaluate()) {
+    return _environment.evaluateMapBody(evaluate);
+  }
+
+  @override
   bool get enableAssertions => true;
 }