Extract _ConstLiteralVerifier and split it into literal / operation methods.

R=brianwilkerson@google.com

Change-Id: I4e3412625cd8c1350bdf7dbc32d92000bc53600e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96986
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index 7234756..d2cbd86 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -2,8 +2,6 @@
 // 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 'dart:collection';
-
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
@@ -146,21 +144,15 @@
     if (node.isConst) {
       InterfaceType nodeType = node.staticType;
       DartType elementType = nodeType.typeArguments[0];
-      // Dummy sets of keys to accommodate the fact that
-      // `_validateCollectionElement` handles map literals.
-      HashSet<DartObject> keys = new HashSet<DartObject>();
-      List<Expression> invalidKeys = new List<Expression>();
+      var verifier = _ConstLiteralVerifier(
+        this,
+        isConst: true,
+        errorCode: CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT,
+        forList: true,
+        listElementType: elementType,
+      );
       for (CollectionElement element in node.elements2) {
-        bool isValid = _validateCollectionElement(element, true, keys,
-            invalidKeys, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT,
-            forList: true, listElementType: elementType);
-        if (isValid && element is Expression) {
-          // TODO(brianwilkerson) Handle the other kinds of elements.
-          _reportErrorIfFromDeferredLibrary(
-              element,
-              CompileTimeErrorCode
-                  .NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY);
-        }
+        verifier.verify(element);
       }
     }
   }
@@ -175,50 +167,50 @@
   void visitSetOrMapLiteral(SetOrMapLiteral node) {
     super.visitSetOrMapLiteral(node);
     bool isConst = node.isConst;
-    HashSet<DartObject> keys = new HashSet<DartObject>();
-    List<Expression> invalidKeys = new List<Expression>();
     if (node.isSet) {
       if (isConst) {
         InterfaceType nodeType = node.staticType;
         var elementType = nodeType.typeArguments[0];
-        var uniqueValues = Set<DartObject>();
         var duplicateElements = <Expression>[];
+        var verifier = _ConstLiteralVerifier(
+          this,
+          isConst: isConst,
+          errorCode: CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT,
+          forSet: true,
+          setElementType: elementType,
+          setUniqueValues: Set<DartObject>(),
+          setDuplicateElements: duplicateElements,
+        );
         for (CollectionElement element in node.elements2) {
-          _validateCollectionElement(element, isConst, keys, invalidKeys,
-              CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT,
-              forSet: true,
-              setElementType: elementType,
-              setElements: uniqueValues,
-              setElementsDuplicate: duplicateElements);
+          verifier.verify(element);
         }
-        for (var invalidElement in duplicateElements) {
+        for (var duplicateElement in duplicateElements) {
           _errorReporter.reportErrorForNode(
-              StaticWarningCode.EQUAL_VALUES_IN_CONST_SET, invalidElement);
+            StaticWarningCode.EQUAL_VALUES_IN_CONST_SET,
+            duplicateElement,
+          );
         }
       }
     } else if (node.isMap) {
       bool reportEqualKeys = true;
+      var duplicateKeyElements = <Expression>[];
+      var verifier = _ConstLiteralVerifier(
+        this,
+        isConst: isConst,
+        errorCode: CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT,
+        forMap: true,
+        mapUniqueKeys: Set<DartObject>(),
+        mapDuplicateKeyElements: duplicateKeyElements,
+      );
       for (CollectionElement entry in node.elements2) {
-        if (entry is MapLiteralEntry) {
-          // TODO(mfairhurst): Change non-const error to a hint, and report
-          // duplicates in constant evaluator instead.
-          // TODO(mfairhurst): unify this with _validateCollectionElemet
-          if (!_validateMapLiteralEntry(entry, isConst, keys, invalidKeys)) {
-            reportEqualKeys = false;
-          }
-        } else {
-          bool isValid = _validateCollectionElement(entry, isConst, keys,
-              invalidKeys, CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT,
-              forMap: true);
-          if (isValid) {
-            // TODO(mfarihurst): handle deferred library checks
-          }
-        }
+        verifier.verify(entry);
       }
       if (reportEqualKeys) {
-        for (int i = 0; i < invalidKeys.length; i++) {
+        for (var duplicateKeyElement in duplicateKeyElements) {
           _errorReporter.reportErrorForNode(
-              StaticWarningCode.EQUAL_KEYS_IN_MAP, invalidKeys[i]);
+            StaticWarningCode.EQUAL_KEYS_IN_MAP,
+            duplicateKeyElement,
+          );
         }
       }
     }
@@ -437,178 +429,6 @@
     return result;
   }
 
-  bool _validateCollectionElement(
-    CollectionElement element,
-    bool isConst,
-    HashSet<DartObject> keys,
-    List<Expression> invalidKeys,
-    ErrorCode errorCode, {
-    DartType listElementType,
-    DartType mapKeyType,
-    DartType mapValueType,
-    DartType setElementType,
-    Set<DartObject> setElements,
-    List<CollectionElement> setElementsDuplicate,
-    bool forList = false,
-    bool forMap = false,
-    bool forSet = false,
-  }) {
-    if (element is Expression) {
-      if (!isConst) return true;
-
-      var value = _validate(element, errorCode);
-      if (value == null) return false;
-
-      if (forList) {
-        if (!_evaluationEngine.runtimeTypeMatch(value, listElementType)) {
-          _errorReporter.reportErrorForNode(
-            StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE,
-            element,
-            [value.type, listElementType],
-          );
-          return false;
-        }
-      }
-
-      if (forSet) {
-        if (!_evaluationEngine.runtimeTypeMatch(value, setElementType)) {
-          _errorReporter.reportErrorForNode(
-            StaticWarningCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE,
-            element,
-            [value.type, setElementType],
-          );
-          return false;
-        }
-
-        if (_implementsEqualsWhenNotAllowed(value.type)) {
-          _errorReporter.reportErrorForNode(
-              CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS,
-              element,
-              [value.type.displayName]);
-          return false;
-        }
-
-        _reportErrorIfFromDeferredLibrary(
-          element,
-          CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT_FROM_DEFERRED_LIBRARY,
-        );
-
-        if (!setElements.add(value)) {
-          setElementsDuplicate.add(element);
-        }
-      }
-
-      return true;
-    } else if (element is ForElement) {
-      if (isConst) {
-        _errorReporter.reportErrorForNode(errorCode, element);
-        return false;
-      }
-      return true;
-    } else if (element is IfElement) {
-      if (isConst) {
-        DartObject conditionResult = _validate(element.condition, errorCode);
-        bool conditionValue = conditionResult?.toBoolValue();
-        if (conditionValue == null) {
-          // The errors have already been reported.
-          return false;
-        } else if (conditionValue) {
-          return _validateCollectionElement(
-                element.thenElement,
-                isConst,
-                keys,
-                invalidKeys,
-                errorCode,
-                forList: forList,
-                forMap: forMap,
-                forSet: forSet,
-                listElementType: listElementType,
-                mapKeyType: mapKeyType,
-                mapValueType: mapValueType,
-                setElementType: setElementType,
-                setElements: setElements,
-                setElementsDuplicate: setElementsDuplicate,
-              ) !=
-              null;
-        } else if (element.elseElement != null) {
-          return _validateCollectionElement(
-                element.elseElement,
-                isConst,
-                keys,
-                invalidKeys,
-                errorCode,
-                forList: forList,
-                forMap: forMap,
-                forSet: forSet,
-                listElementType: listElementType,
-                mapKeyType: mapKeyType,
-                mapValueType: mapValueType,
-                setElementType: setElementType,
-                setElements: setElements,
-                setElementsDuplicate: setElementsDuplicate,
-              ) !=
-              null;
-        } else {
-          return true;
-        }
-      }
-      return true;
-    } else if (element is MapLiteralEntry) {
-      return _validateMapLiteralEntry(element, isConst, keys, invalidKeys);
-    } else if (element is SpreadElement) {
-      if (!isConst) return true;
-
-      var value = _validate(element.expression, errorCode);
-      if (value == null) return false;
-
-      if (forList || forSet) {
-        var listValue = value.toListValue();
-        var setValue = value.toSetValue();
-        if (listValue != null ||
-            setValue != null ||
-            value.isNull && _isNullableSpread(element)) {
-          if (listValue != null) {
-            var elementType = value.type.typeArguments[0];
-            if (_implementsEqualsWhenNotAllowed(elementType)) {
-              _errorReporter.reportErrorForNode(
-                CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS,
-                element,
-                [elementType.displayName],
-              );
-            }
-          }
-          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
@@ -757,52 +577,6 @@
       _validateInitializerExpression(parameterElements, argument);
     }
   }
-
-  bool _validateMapLiteralEntry(MapLiteralEntry entry, bool isConst,
-      HashSet<DartObject> keys, List<Expression> invalidKeys) {
-    Expression key = entry.key;
-    if (isConst) {
-      DartObjectImpl keyResult =
-          _validate(key, CompileTimeErrorCode.NON_CONSTANT_MAP_KEY);
-      Expression valueExpression = entry.value;
-      DartObjectImpl valueResult = _validate(
-          valueExpression, CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE);
-      if (valueResult != null) {
-        _reportErrorIfFromDeferredLibrary(valueExpression,
-            CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY);
-      }
-      if (keyResult != null) {
-        _reportErrorIfFromDeferredLibrary(key,
-            CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY);
-        if (!keys.add(keyResult)) {
-          invalidKeys.add(key);
-        }
-        DartType type = keyResult.type;
-        if (_implementsEqualsWhenNotAllowed(type)) {
-          _errorReporter.reportErrorForNode(
-              CompileTimeErrorCode
-                  .CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
-              key,
-              [type.displayName]);
-        }
-      }
-    } else {
-      // Note: we throw the errors away because this isn't actually a const.
-      AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
-      ErrorReporter subErrorReporter =
-          new ErrorReporter(errorListener, _errorReporter.source);
-      DartObjectImpl result =
-          key.accept(new ConstantVisitor(_evaluationEngine, subErrorReporter));
-      if (result != null) {
-        if (!keys.add(result)) {
-          invalidKeys.add(key);
-        }
-      } else {
-        return false;
-      }
-    }
-    return true;
-  }
 }
 
 class _ConstantVerifier_validateInitializerExpression extends ConstantVisitor {
@@ -864,3 +638,259 @@
     return super.visitSimpleIdentifier(node);
   }
 }
+
+class _ConstLiteralVerifier {
+  final ConstantVerifier verifier;
+  final bool isConst;
+  final Set<DartObject> mapUniqueKeys;
+  final List<Expression> mapDuplicateKeyElements;
+  final ErrorCode errorCode;
+  final DartType listElementType;
+  final DartType mapKeyType;
+  final DartType mapValueType;
+  final DartType setElementType;
+  final Set<DartObject> setUniqueValues;
+  final List<CollectionElement> setDuplicateElements;
+  final bool forList;
+  final bool forMap;
+  final bool forSet;
+
+  _ConstLiteralVerifier(
+    this.verifier, {
+    this.isConst,
+    this.mapUniqueKeys,
+    this.mapDuplicateKeyElements,
+    this.errorCode,
+    this.listElementType,
+    this.mapKeyType,
+    this.mapValueType,
+    this.setElementType,
+    this.setUniqueValues,
+    this.setDuplicateElements,
+    this.forList = false,
+    this.forMap = false,
+    this.forSet = false,
+  });
+
+  bool verify(CollectionElement element) {
+    if (element is Expression) {
+      if (!isConst) return true;
+
+      var value = verifier._validate(element, errorCode);
+      if (value == null) return false;
+
+      if (forList) {
+        return _validateListExpression(element, value);
+      }
+
+      if (forSet) {
+        return _validateSetExpression(element, value);
+      }
+
+      return true;
+    } else if (element is ForElement) {
+      if (!isConst) return true;
+
+      verifier._errorReporter.reportErrorForNode(errorCode, element);
+      return false;
+    } else if (element is IfElement) {
+      if (!isConst) return true;
+
+      DartObject conditionResult =
+          verifier._validate(element.condition, errorCode);
+      bool conditionValue = conditionResult?.toBoolValue();
+      if (conditionValue == null) {
+        // The errors have already been reported.
+        return false;
+      } else if (conditionValue) {
+        return verify(element.thenElement);
+      } else if (element.elseElement != null) {
+        return verify(element.elseElement);
+      } else {
+        return true;
+      }
+    } else if (element is MapLiteralEntry) {
+      return _validateMapLiteralEntry(element);
+    } else if (element is SpreadElement) {
+      if (!isConst) return true;
+
+      var value = verifier._validate(element.expression, errorCode);
+      if (value == null) return false;
+
+      if (forList || forSet) {
+        return _validateListOrSetSpread(element, value);
+      }
+
+      if (forMap) {
+        return _validateMapSpread(element, value);
+      }
+
+      return true;
+    }
+    throw new UnsupportedError(
+      'Unhandled type of collection element: ${element.runtimeType}',
+    );
+  }
+
+  bool _validateListExpression(Expression expression, DartObjectImpl value) {
+    if (!verifier._evaluationEngine.runtimeTypeMatch(value, listElementType)) {
+      verifier._errorReporter.reportErrorForNode(
+        StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE,
+        expression,
+        [value.type, listElementType],
+      );
+      return false;
+    }
+
+    verifier._reportErrorIfFromDeferredLibrary(
+      expression,
+      CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY,
+    );
+
+    return true;
+  }
+
+  bool _validateListOrSetSpread(SpreadElement element, DartObjectImpl value) {
+    var listValue = value.toListValue();
+    var setValue = value.toSetValue();
+
+    if (listValue == null && setValue == null) {
+      if (value.isNull && _isNullableSpread(element)) {
+        return true;
+      }
+      verifier._errorReporter.reportErrorForNode(
+        CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
+        element.expression,
+      );
+      return false;
+    }
+
+    if (listValue != null) {
+      var elementType = value.type.typeArguments[0];
+      if (verifier._implementsEqualsWhenNotAllowed(elementType)) {
+        verifier._errorReporter.reportErrorForNode(
+          CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS,
+          element,
+          [elementType],
+        );
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  bool _validateMapLiteralEntry(MapLiteralEntry entry) {
+    if (!forMap) return false;
+
+    var keyExpression = entry.key;
+    if (isConst) {
+      var keyValue = verifier._validate(
+        keyExpression,
+        CompileTimeErrorCode.NON_CONSTANT_MAP_KEY,
+      );
+
+      var valueExpression = entry.value;
+      var valueValue = verifier._validate(
+        valueExpression,
+        CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE,
+      );
+
+      if (keyValue != null) {
+        verifier._reportErrorIfFromDeferredLibrary(
+          keyExpression,
+          CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY,
+        );
+
+        if (!mapUniqueKeys.add(keyValue)) {
+          mapDuplicateKeyElements.add(keyExpression);
+        }
+
+        var keyType = keyValue.type;
+        if (verifier._implementsEqualsWhenNotAllowed(keyType)) {
+          verifier._errorReporter.reportErrorForNode(
+            CompileTimeErrorCode
+                .CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
+            keyExpression,
+            [keyType],
+          );
+        }
+      }
+
+      if (valueValue != null) {
+        verifier._reportErrorIfFromDeferredLibrary(
+          valueExpression,
+          CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY,
+        );
+      }
+    } else {
+      // Note: we throw the errors away because this isn't actually a const.
+      var nullErrorReporter = new ErrorReporter(
+        AnalysisErrorListener.NULL_LISTENER,
+        verifier._errorReporter.source,
+      );
+      var keyValue = keyExpression.accept(
+        new ConstantVisitor(verifier._evaluationEngine, nullErrorReporter),
+      );
+
+      if (keyValue != null) {
+        if (!mapUniqueKeys.add(keyValue)) {
+          mapDuplicateKeyElements.add(keyExpression);
+        }
+      }
+
+      return true;
+    }
+    return true;
+  }
+
+  bool _validateMapSpread(SpreadElement element, DartObjectImpl value) {
+    if (value.toMapValue() != null ||
+        value.isNull && _isNullableSpread(element)) {
+      return true;
+    }
+
+    verifier._errorReporter.reportErrorForNode(
+      CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP,
+      element.expression,
+    );
+
+    return false;
+  }
+
+  bool _validateSetExpression(Expression expression, DartObjectImpl value) {
+    if (!verifier._evaluationEngine.runtimeTypeMatch(value, setElementType)) {
+      verifier._errorReporter.reportErrorForNode(
+        StaticWarningCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE,
+        expression,
+        [value.type, setElementType],
+      );
+      return false;
+    }
+
+    if (verifier._implementsEqualsWhenNotAllowed(value.type)) {
+      verifier._errorReporter.reportErrorForNode(
+        CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS,
+        expression,
+        [value.type],
+      );
+      return false;
+    }
+
+    verifier._reportErrorIfFromDeferredLibrary(
+      expression,
+      CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT_FROM_DEFERRED_LIBRARY,
+    );
+
+    if (!setUniqueValues.add(value)) {
+      setDuplicateElements.add(expression);
+    }
+
+    return true;
+  }
+
+  static bool _isNullableSpread(SpreadElement element) {
+    return element.spreadOperator.type ==
+        TokenType.PERIOD_PERIOD_PERIOD_QUESTION;
+  }
+}
diff --git a/pkg/analyzer/test/generated/compile_time_error_code_test.dart b/pkg/analyzer/test/generated/compile_time_error_code_test.dart
index e4340d6..959fd3e 100644
--- a/pkg/analyzer/test/generated/compile_time_error_code_test.dart
+++ b/pkg/analyzer/test/generated/compile_time_error_code_test.dart
@@ -457,13 +457,6 @@
     verify([source]);
   }
 
-  test_mapKeyTypeNotAssignable_const() async {
-    Source source = addSource("var v = const <String, int >{1 : 2};");
-    await computeAnalysisResult(source);
-    assertErrors(source, [StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE]);
-    verify([source]);
-  }
-
   test_mapValueTypeNotAssignable_const() async {
     Source source = addSource("var v = const <String, String>{'a' : 2};");
     await computeAnalysisResult(source);