Generate an error if a constant set contains two elements with the same value

Change-Id: Ie359a1ea639e9c928df32c727bcd2f075f92033f
Reviewed-on: https://dart-review.googlesource.com/c/88425
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 41df45e..7ffa3f6 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -585,6 +585,7 @@
   StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER,
   StaticWarningCode.CONST_WITH_ABSTRACT_CLASS,
   StaticWarningCode.EQUAL_KEYS_IN_MAP,
+  StaticWarningCode.EQUAL_VALUES_IN_CONST_SET,
   StaticWarningCode.EXPORT_DUPLICATED_LIBRARY_NAMED,
   StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS,
   StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS_COULD_BE_NAMED,
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index eaba4b2..30e9486 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -180,10 +180,8 @@
         if (keyResult != null) {
           _reportErrorIfFromDeferredLibrary(key,
               CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY);
-          if (keys.contains(keyResult)) {
+          if (!keys.add(keyResult)) {
             invalidKeys.add(key);
-          } else {
-            keys.add(keyResult);
           }
           DartType type = keyResult.type;
           if (_implementsEqualsWhenNotAllowed(type)) {
@@ -203,10 +201,8 @@
         DartObjectImpl result = key
             .accept(new ConstantVisitor(_evaluationEngine, subErrorReporter));
         if (result != null) {
-          if (keys.contains(result)) {
+          if (!keys.add(result)) {
             invalidKeys.add(key);
-          } else {
-            keys.add(result);
           }
         } else {
           reportEqualKeys = false;
@@ -214,8 +210,7 @@
       }
     }
     if (reportEqualKeys) {
-      int length = invalidKeys.length;
-      for (int i = 0; i < length; i++) {
+      for (int i = 0; i < invalidKeys.length; i++) {
         _errorReporter.reportErrorForNode(
             StaticWarningCode.EQUAL_KEYS_IN_MAP, invalidKeys[i]);
       }
@@ -231,16 +226,20 @@
   @override
   void visitSetLiteral(SetLiteral node) {
     super.visitSetLiteral(node);
+    HashSet<DartObject> elements = new HashSet<DartObject>();
+    List<Expression> invalidElements = new List<Expression>();
     if (node.isConst) {
-      DartObjectImpl result;
       for (Expression element in node.elements) {
-        result =
+        DartObjectImpl result =
             _validate(element, CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT);
         if (result != null) {
           _reportErrorIfFromDeferredLibrary(
               element,
               CompileTimeErrorCode
                   .NON_CONSTANT_SET_ELEMENT_FROM_DEFERRED_LIBRARY);
+          if (!elements.add(result)) {
+            invalidElements.add(element);
+          }
           DartType type = result.type;
           if (_implementsEqualsWhenNotAllowed(type)) {
             _errorReporter.reportErrorForNode(
@@ -250,6 +249,10 @@
           }
         }
       }
+      for (var invalidElement in invalidElements) {
+        _errorReporter.reportErrorForNode(
+            StaticWarningCode.EQUAL_VALUES_IN_CONST_SET, invalidElement);
+      }
     }
   }
 
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index a3cff21..b6f9cc0 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -3661,6 +3661,14 @@
       'EQUAL_KEYS_IN_MAP', "Two keys in a map literal can't be equal.");
 
   /**
+   * It is a compile-time error if any two of the values in a constant set are
+   * equal according to `==`.
+   */
+  static const StaticWarningCode EQUAL_VALUES_IN_CONST_SET =
+      const StaticWarningCode('EQUAL_VALUES_IN_CONST_SET',
+          "Two values in a constant set can't be equal.");
+
+  /**
    * 14.2 Exports: It is a static warning to export two different libraries with
    * the same name.
    *
diff --git a/pkg/analyzer/test/generated/static_warning_code_test.dart b/pkg/analyzer/test/generated/static_warning_code_test.dart
index 08e53a9..f5b8968 100644
--- a/pkg/analyzer/test/generated/static_warning_code_test.dart
+++ b/pkg/analyzer/test/generated/static_warning_code_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/source_io.dart';
 import 'package:test/test.dart';
@@ -13,11 +14,54 @@
 
 main() {
   defineReflectiveSuite(() {
+    defineReflectiveTests(EqualValuesInConstSetTest);
     defineReflectiveTests(StaticWarningCodeTest);
   });
 }
 
 @reflectiveTest
+class EqualValuesInConstSetTest extends ResolverTestCase {
+  @override
+  List<String> get enabledExperiments => [EnableString.set_literals];
+
+  @override
+  bool get enableNewAnalysisDriver => true;
+
+  test_simpleValues() async {
+    Source source = addSource('var s = const {0, 1, 0};');
+    await computeAnalysisResult(source);
+    assertErrors(source, [StaticWarningCode.EQUAL_VALUES_IN_CONST_SET]);
+    verify([source]);
+  }
+
+  test_valuesWithEqualTypeParams() async {
+    Source source = addSource(r'''
+class A<T> {
+  const A();
+}
+var s = const {A<int>(), A<int>()};
+''');
+    await computeAnalysisResult(source);
+    assertErrors(source, [StaticWarningCode.EQUAL_VALUES_IN_CONST_SET]);
+    verify([source]);
+  }
+
+  test_valuesWithUnequalTypeParams() async {
+    // No error should be produced because A<int> and A<num> are different
+    // types.
+    Source source = addSource(r'''
+class A<T> {
+  const A();
+}
+const s = {A<int>(), A<num>()};
+''');
+    await computeAnalysisResult(source);
+    assertNoErrors(source);
+    verify([source]);
+  }
+}
+
+@reflectiveTest
 class StaticWarningCodeTest extends ResolverTestCase {
   fail_argumentTypeNotAssignable_tearOff_required() async {
     Source source = addSource(r'''