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();