[analyzer] Allow spreads between const lists and sets in the const evaluator

The change allows const lists to be in the spread of a const set and vice-versa. It also reports the correct error if we use a map in a list/set (and vice-versa) which is necessary when we combine all the error reporting in the `ConstantVisitor` to one location.

Adds some additional tests to make sure the behaviour we want is still working.

Change-Id: I0f64d58c857bd905ac8521346cd34a113b6d4a40
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/323780
Commit-Queue: Kallen Tu <kallentu@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Kallen Tu 2023-09-11 23:39:30 +00:00 committed by Commit Queue
parent 4a04ec3201
commit f2e11bdeee
3 changed files with 188 additions and 2 deletions

View file

@ -605,6 +605,10 @@ class ConstantVerifier extends RecursiveAstVisitor<void> {
identical(dataErrorCode,
CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH) ||
identical(dataErrorCode, CompileTimeErrorCode.CONST_TYPE_PARAMETER) ||
identical(
dataErrorCode, CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP) ||
identical(dataErrorCode,
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET) ||
identical(
dataErrorCode,
CompileTimeErrorCode

View file

@ -1264,6 +1264,17 @@ class ConstantVisitor extends UnifyingAstVisitor<Constant> {
for (CollectionElement element in node.elements) {
var result = _addElementsToMap(map, element);
if (result is InvalidConstant) {
if (result.errorCode ==
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP &&
node.isMap) {
// Only report the error if we know this is a non-ambiguous map.
// TODO(kallentu): Don't report error here and avoid reporting this
// error in this case.
_errorReporter.reportErrorForOffset(
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP,
result.entity.offset,
result.entity.length);
}
return result;
}
}
@ -1404,8 +1415,16 @@ class ConstantVisitor extends UnifyingAstVisitor<Constant> {
case InvalidConstant():
return spread;
case DartObjectImpl():
var listValue = spread.toListValue();
// Special case for ...?
if (spread.isNull && element.isNullAware) {
return null;
}
var listValue = spread.toListValue() ?? spread.toSetValue();
if (listValue == null) {
// TODO(kallentu): Don't report error here.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
element.expression);
return InvalidConstant(element.expression,
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET);
}
@ -1471,6 +1490,10 @@ class ConstantVisitor extends UnifyingAstVisitor<Constant> {
case InvalidConstant():
return spread;
case DartObjectImpl():
// Special case for ...?
if (spread.isNull && element.isNullAware) {
return null;
}
var mapValue = spread.toMapValue();
if (mapValue == null) {
return InvalidConstant(element.expression,
@ -1533,8 +1556,16 @@ class ConstantVisitor extends UnifyingAstVisitor<Constant> {
case InvalidConstant():
return spread;
case DartObjectImpl():
var setValue = spread.toSetValue();
// Special case for ...?
if (spread.isNull && element.isNullAware) {
return null;
}
var setValue = spread.toSetValue() ?? spread.toListValue();
if (setValue == null) {
// TODO(kallentu): Don't report error here.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
element.expression);
return InvalidConstant(element.expression,
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET);
}

View file

@ -2050,6 +2050,43 @@ const x = <int>[...a];
_assertNull(result);
}
test_visitListLiteral_spreadElement_null() async {
await assertNoErrorsInCode('''
const a = null;
const List<String> x = [
'anotherString',
...?a,
];
''');
final result = _topLevelVar('x');
assertDartObjectText(result, '''
List
elementType: String
elements
String anotherString
variable: self::@variable::x
''');
}
test_visitListLiteral_spreadElement_set() async {
await assertNoErrorsInCode('''
const a = {'string'};
const List<String> x = [
'anotherString',
...a,
];
''');
final result = _topLevelVar('x');
assertDartObjectText(result, '''
List
elementType: String
elements
String anotherString
String string
variable: self::@variable::x
''');
}
test_visitMethodInvocation_notIdentical() async {
await assertErrorsInCode(r'''
int f() {
@ -2373,6 +2410,34 @@ Record(int, String, {bool c})
''');
}
test_visitSetOrMapLiteral_ambiguous() async {
await assertErrorsInCode(r'''
const l = [];
const ambiguous = {...l, 1: 2};
''', [
error(CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_BOTH, 32, 12),
]);
}
test_visitSetOrMapLiteral_ambiguous_either() async {
await assertErrorsInCode(r'''
const int? i = 1;
const res = {...?i};
''', [
error(CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_EITHER, 31, 7),
]);
}
test_visitSetOrMapLiteral_ambiguous_inList() async {
await assertErrorsInCode(r'''
const l = [];
const ambiguous = {...l, 1: 2};
const anotherList = [...ambiguous];
''', [
error(CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_BOTH, 32, 12),
]);
}
test_visitSetOrMapLiteral_map_complexKey() async {
await assertNoErrorsInCode(r'''
class A {
@ -2454,6 +2519,57 @@ Map
''');
}
test_visitSetOrMapLiteral_map_spread() async {
await assertNoErrorsInCode('''
const x = {'string': 1};
const Map<String, int> alwaysInclude = {
'anotherString': 0,
...x,
};
''');
final result = _topLevelVar('x');
assertDartObjectText(result, '''
Map
entries
entry
key: String string
value: int 1
variable: self::@variable::x
''');
}
test_visitSetOrMapLiteral_map_spread_notMap() async {
await assertErrorsInCode('''
const x = ['string'];
const Map<String, int> alwaysInclude = {
'anotherString': 0,
...x,
};
''', [
error(CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP, 90, 1),
error(CompileTimeErrorCode.NOT_MAP_SPREAD, 90, 1),
]);
}
test_visitSetOrMapLiteral_map_spread_null() async {
await assertNoErrorsInCode('''
const a = null;
const Map<String, int> x = {
'anotherString': 0,
...?a,
};
''');
final result = _topLevelVar('x');
assertDartObjectText(result, '''
Map
entries
entry
key: String anotherString
value: int 0
variable: self::@variable::x
''');
}
test_visitSetOrMapLiteral_set_double_zeros() async {
await assertNoErrorsInCode(r'''
class C {
@ -2504,6 +2620,41 @@ const c = const {if (nonBool) 3};
_assertNull(result);
}
test_visitSetOrMapLiteral_set_spread_list() async {
await assertNoErrorsInCode('''
const a = ['string'];
const Set<String> x = {
'anotherString',
...a,
};
''');
final result = _topLevelVar('x');
assertDartObjectText(result, '''
Set
elements
String anotherString
String string
variable: self::@variable::x
''');
}
test_visitSetOrMapLiteral_set_spread_null() async {
await assertNoErrorsInCode('''
const a = null;
const Set<String> x = {
'anotherString',
...?a,
};
''');
final result = _topLevelVar('x');
assertDartObjectText(result, '''
Set
elements
String anotherString
variable: self::@variable::x
''');
}
test_visitSimpleIdentifier_className() async {
await assertNoErrorsInCode('''
const a = C;