Report CONST_SPREAD_EXPECTED_LIST_OR_SET and CONST_SPREAD_EXPECTED_MAP.

R=brianwilkerson@google.com, paulberry@google.com

Change-Id: I70eb31a19c447d17b4cc60ae2ff3caadb35cc6bd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96712
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index a452f10..8475ef8 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -112,6 +112,8 @@
   CompileTimeErrorCode.CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
   CompileTimeErrorCode.CONST_NOT_INITIALIZED,
   CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS,
+  CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
+  CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP,
   CompileTimeErrorCode.CONST_WITH_INVALID_TYPE_PARAMETERS,
   CompileTimeErrorCode.CONST_WITH_NON_CONST,
   CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT,
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index ddffed2..43da200 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -6,6 +6,7 @@
 
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/constant/value.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -149,7 +150,8 @@
       List<Expression> invalidKeys = new List<Expression>();
       for (CollectionElement element in node.elements2) {
         bool isValid = _validateCollectionElement(element, true, keys,
-            invalidKeys, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT);
+            invalidKeys, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT,
+            forList: true);
         if (isValid && element is Expression) {
           // TODO(brianwilkerson) Handle the other kinds of elements.
           _reportErrorIfFromDeferredLibrary(
@@ -201,7 +203,8 @@
             }
           } else {
             bool isValid = _validateCollectionElement(element, isConst, keys,
-                invalidKeys, CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT);
+                invalidKeys, CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT,
+                forSet: true);
             if (isValid) {
               // TODO(mfairhurst) report deferred library error
             }
@@ -224,7 +227,8 @@
           }
         } else {
           bool isValid = _validateCollectionElement(entry, isConst, keys,
-              invalidKeys, CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT);
+              invalidKeys, CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT,
+              forMap: true);
           if (isValid) {
             // TODO(mfarihurst): handle deferred library checks
           }
@@ -453,11 +457,15 @@
   }
 
   bool _validateCollectionElement(
-      CollectionElement element,
-      bool isConst,
-      HashSet<DartObject> keys,
-      List<Expression> invalidKeys,
-      ErrorCode errorCode) {
+    CollectionElement element,
+    bool isConst,
+    HashSet<DartObject> keys,
+    List<Expression> invalidKeys,
+    ErrorCode errorCode, {
+    bool forList = false,
+    bool forMap = false,
+    bool forSet = false,
+  }) {
     if (element is Expression) {
       return !isConst || _validate(element, errorCode) != null;
     } else if (element is ForElement) {
@@ -489,12 +497,47 @@
     } else if (element is MapLiteralEntry) {
       return _validateMapLiteralEntry(element, isConst, keys, invalidKeys);
     } else if (element is SpreadElement) {
-      return !isConst || _validate(element.expression, errorCode) != null;
+      if (!isConst) return true;
+
+      var value = _validate(element.expression, errorCode);
+      if (value == null) return false;
+
+      if (forList || forSet) {
+        if (value.toListValue() != null ||
+            value.toSetValue() != null ||
+            value.isNull && _isNullableSpread(element)) {
+          return true;
+        }
+        _errorReporter.reportErrorForNode(
+          CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
+          element.expression,
+        );
+        return false;
+      }
+
+      if (forMap) {
+        if (value.toMapValue() != null ||
+            value.isNull && _isNullableSpread(element)) {
+          return true;
+        }
+        _errorReporter.reportErrorForNode(
+          CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP,
+          element.expression,
+        );
+        return false;
+      }
+
+      return true;
     }
     throw new UnsupportedError(
         'Unhandled type of collection element: ${element.runtimeType}');
   }
 
+  static bool _isNullableSpread(SpreadElement element) {
+    return element.spreadOperator.type ==
+        TokenType.PERIOD_PERIOD_PERIOD_QUESTION;
+  }
+
   /// Validate that if the passed arguments are constant expressions.
   ///
   /// @param argumentList the argument list to evaluate
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index acfcfef..f3d432d 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -654,6 +654,14 @@
           correction:
               "Try declaring the field as final, or adding the keyword 'static'.");
 
+  static const CompileTimeErrorCode CONST_SPREAD_EXPECTED_LIST_OR_SET =
+      const CompileTimeErrorCode('CONST_SPREAD_EXPECTED_LIST_OR_SET',
+          "A list or a set is expected in this spread.");
+
+  static const CompileTimeErrorCode CONST_SPREAD_EXPECTED_MAP =
+      const CompileTimeErrorCode(
+          'CONST_SPREAD_EXPECTED_MAP', "A map is expected in this spread.");
+
   /**
    * 12.8 Maps: It is a compile-time error if the key of an entry in a constant
    * map literal is an instance of a class that implements the operator
diff --git a/pkg/analyzer/test/src/diagnostics/const_spread_expected_list_or_set_test.dart b/pkg/analyzer/test/src/diagnostics/const_spread_expected_list_or_set_test.dart
new file mode 100644
index 0000000..9c8d9f4
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/const_spread_expected_list_or_set_test.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/driver_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConstSpreadExpectedListOrSetTest);
+  });
+}
+
+@reflectiveTest
+class ConstSpreadExpectedListOrSetTest extends DriverResolutionTest {
+  @override
+  AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
+    ..enabledExperiments = ['control-flow-collections', 'spread-collections'];
+
+  test_const_listInt() async {
+    await assertErrorsInCode('''
+const dynamic a = 5;
+var b = const <int>[...a];
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET]);
+  }
+
+  test_const_listList() async {
+    await assertNoErrorsInCode('''
+const dynamic a = [5];
+var b = const <int>[...a];
+''');
+  }
+
+  test_const_listMap() async {
+    await assertErrorsInCode('''
+const dynamic a = <int, int>{0: 1};
+var b = const <int>[...a];
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET]);
+  }
+
+  test_const_listNull() async {
+    await assertErrorsInCode('''
+const dynamic a = null;
+var b = const <int>[...a];
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET]);
+  }
+
+  test_const_listNull_nullable() async {
+    await assertNoErrorsInCode('''
+const dynamic a = null;
+var b = const <int>[...?a];
+''');
+  }
+
+  test_const_listSet() async {
+    await assertNoErrorsInCode('''
+const dynamic a = <int>{5};
+var b = const <int>[...a];
+''');
+  }
+
+  test_const_setInt() async {
+    await assertErrorsInCode('''
+const dynamic a = 5;
+var b = const <int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET]);
+  }
+
+  test_const_setList() async {
+    await assertNoErrorsInCode('''
+const dynamic a = <int>[5];
+var b = const <int>{...a};
+''');
+  }
+
+  test_const_setMap() async {
+    await assertErrorsInCode('''
+const dynamic a = <int, int>{1: 2};
+var b = const <int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET]);
+  }
+
+  test_const_setNull() async {
+    await assertErrorsInCode('''
+const dynamic a = null;
+var b = const <int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET]);
+  }
+
+  test_const_setNull_nullable() async {
+    await assertNoErrorsInCode('''
+const dynamic a = null;
+var b = const <int>{...?a};
+''');
+  }
+
+  test_const_setSet() async {
+    await assertNoErrorsInCode('''
+const dynamic a = <int>{5};
+var b = const <int>{...a};
+''');
+  }
+
+  test_nonConst_listInt() async {
+    await assertNoErrorsInCode('''
+const dynamic a = 5;
+var b = <int>[...a];
+''');
+  }
+
+  test_nonConst_setInt() async {
+    await assertNoErrorsInCode('''
+const dynamic a = 5;
+var b = <int>{...a};
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/const_spread_expected_map_test.dart b/pkg/analyzer/test/src/diagnostics/const_spread_expected_map_test.dart
new file mode 100644
index 0000000..405a700
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/const_spread_expected_map_test.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/driver_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConstSpreadExpectedMapTest);
+  });
+}
+
+@reflectiveTest
+class ConstSpreadExpectedMapTest extends DriverResolutionTest {
+  @override
+  AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
+    ..enabledExperiments = ['control-flow-collections', 'spread-collections'];
+
+  test_const_mapInt() async {
+    await assertErrorsInCode('''
+const dynamic a = 5;
+var b = const <int, int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP]);
+  }
+
+  test_const_mapList() async {
+    await assertErrorsInCode('''
+const dynamic a = <int>[5];
+var b = const <int, int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP]);
+  }
+
+  test_const_mapMap() async {
+    await assertNoErrorsInCode('''
+const dynamic a = <int, int>{1: 2};
+var b = <int, int>{...a};
+''');
+  }
+
+  test_const_mapNull() async {
+    await assertErrorsInCode('''
+const dynamic a = null;
+var b = const <int, int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP]);
+  }
+
+  test_const_mapNull_nullable() async {
+    await assertNoErrorsInCode('''
+const dynamic a = null;
+var b = <int, int>{...?a};
+''');
+  }
+
+  test_const_mapSet() async {
+    await assertErrorsInCode('''
+const dynamic a = <int>{5};
+var b = const <int, int>{...a};
+''', [CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP]);
+  }
+
+  test_nonConst_mapInt() async {
+    await assertNoErrorsInCode('''
+const dynamic a = 5;
+var b = <int, int>{...a};
+''');
+  }
+
+  test_nonConst_mapMap() async {
+    await assertNoErrorsInCode('''
+const dynamic a = {1: 2};
+var b = <int, int>{...a};
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index a9a7393..98a3ced 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -9,6 +9,9 @@
 import 'can_be_null_after_null_aware_test.dart' as can_be_null_after_null_aware;
 import 'const_constructor_param_type_mismatch_test.dart'
     as const_constructor_param_type_mismatch;
+import 'const_spread_expected_list_or_set_test.dart'
+    as const_spread_expected_list_or_set;
+import 'const_spread_expected_map_test.dart' as const_spread_expected_map;
 import 'dead_code_test.dart' as dead_code;
 import 'deprecated_member_use_test.dart' as deprecated_member_use;
 import 'division_optimization_test.dart' as division_optimization;
@@ -69,6 +72,8 @@
     argument_type_not_assignable.main();
     can_be_null_after_null_aware.main();
     const_constructor_param_type_mismatch.main();
+    const_spread_expected_list_or_set.main();
+    const_spread_expected_map.main();
     dead_code.main();
     deprecated_member_use.main();
     division_optimization.main();