Version 2.14.0-208.0.dev

Merge commit 'd5e92e1c7ac52c1e5a248ef8afedbb849519729d' into 'dev'
diff --git a/pkg/analyzer/lib/src/dart/element/extensions.dart b/pkg/analyzer/lib/src/dart/element/extensions.dart
index 66ba586..d1421ed 100644
--- a/pkg/analyzer/lib/src/dart/element/extensions.dart
+++ b/pkg/analyzer/lib/src/dart/element/extensions.dart
@@ -52,6 +52,12 @@
 }
 
 extension ParameterElementExtensions on ParameterElement {
+  bool get isParameterOfTopLevelFunction {
+    var enclosing = enclosingElement;
+    return enclosing is FunctionElement &&
+        enclosing.enclosingElement is CompilationUnitElement;
+  }
+
   /// Return [ParameterElement] with the specified properties replaced.
   ParameterElement copyWith({DartType? type, ParameterKind? kind}) {
     return ParameterElementImpl.synthetic(
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
index 586bb59..54882a1 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
@@ -2623,16 +2623,18 @@
    * Type checks of the type `x is! Null` should be done with `x != null`.
    */
   static const HintCode TYPE_CHECK_IS_NOT_NULL = HintCode(
-      'TYPE_CHECK_IS_NOT_NULL',
+      'TYPE_CHECK_WITH_NULL',
       "Tests for non-null should be done with '!= null'.",
-      correction: "Try replacing the 'is! Null' check with '!= null'.");
+      correction: "Try replacing the 'is! Null' check with '!= null'.",
+      uniqueName: 'TYPE_CHECK_IS_NOT_NULL');
 
   /**
    * Type checks of the type `x is Null` should be done with `x == null`.
    */
   static const HintCode TYPE_CHECK_IS_NULL = HintCode(
-      'TYPE_CHECK_IS_NULL', "Tests for null should be done with '== null'.",
-      correction: "Try replacing the 'is Null' check with '== null'.");
+      'TYPE_CHECK_WITH_NULL', "Tests for null should be done with '== null'.",
+      correction: "Try replacing the 'is Null' check with '== null'.",
+      uniqueName: 'TYPE_CHECK_IS_NULL');
 
   /**
    * Parameters:
diff --git a/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
index b05b24d..72d171e 100644
--- a/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
@@ -10,7 +10,6 @@
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
-import 'package:analyzer/src/dart/constant/utilities.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/member.dart';
 import 'package:analyzer/src/dart/element/type.dart';
@@ -43,8 +42,6 @@
     if (elementAnnotationImpl == null) {
       // Analyzer ignores annotations on "part of" directives.
       assert(parent is PartDirective || parent is PartOfDirective);
-    } else if (_resolver.shouldCloneAnnotations) {
-      elementAnnotationImpl.annotationAst = _createCloner().cloneNode(node);
     }
   }
 
@@ -249,14 +246,6 @@
     _resolveConstructorInvocationArguments(node);
   }
 
-  /// Return a newly created cloner that can be used to clone constant
-  /// expressions.
-  ///
-  /// TODO(scheglov) this is duplicate
-  ConstantAstCloner _createCloner() {
-    return ConstantAstCloner();
-  }
-
   void _extensionGetter(
     AnnotationImpl node,
     ExtensionElement extensionElement,
diff --git a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
index 91d1ae7..10c3167 100644
--- a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
@@ -311,8 +311,7 @@
     _elementHolder.enclose(element);
     nameNode.staticElement = element;
 
-    node.metadata.accept(this);
-    element.metadata = _createElementAnnotations(node.metadata);
+    _setOrCreateMetadataElements(element, node.metadata);
 
     element.isConst = node.isConst;
     element.isFinal = node.isFinal;
@@ -469,9 +468,7 @@
       nameNode.staticElement = element;
     }
 
-    element.metadata = _createElementAnnotations(node.metadata);
-    node.metadata.accept(this);
-    _setElementAnnotations(node.metadata, element.metadata);
+    _setOrCreateMetadataElements(element, node.metadata);
 
     _withElementHolder(ElementHolder(element), () {
       _withNameScope(() {
@@ -513,7 +510,6 @@
 
       _setCodeRange(element, node);
       setElementDocumentationComment(element, node);
-      element.metadata = _createElementAnnotations(node.metadata);
 
       var body = node.functionExpression.body;
       if (node.externalKeyword != null || body is NativeFunctionBody) {
@@ -530,8 +526,7 @@
     var expression = node.functionExpression;
     expression.declaredElement = element;
 
-    node.metadata.accept(this);
-    _setElementAnnotations(node.metadata, element.metadata);
+    _setOrCreateMetadataElements(element, node.metadata);
 
     var holder = ElementHolder(element);
     _withElementHolder(holder, () {
@@ -647,8 +642,7 @@
       nameNode.staticElement = element;
     }
 
-    node.metadata.accept(this);
-    element.metadata = _createElementAnnotations(node.metadata);
+    _setOrCreateMetadataElements(element, node.metadata);
 
     var holder = ElementHolder(element);
     _withElementHolder(holder, () {
@@ -778,7 +772,9 @@
     } else {
       for (var annotation in node.metadata) {
         annotation as AnnotationImpl;
-        annotation.elementAnnotation = ElementAnnotationImpl(_unitElement);
+        var elementAnnotation = ElementAnnotationImpl(_unitElement);
+        elementAnnotation.annotationAst = annotation;
+        annotation.elementAnnotation = elementAnnotation;
       }
     }
   }
@@ -887,12 +883,10 @@
 
     node.type?.accept(this);
     if (_elementWalker == null) {
-      element.metadata = _createElementAnnotations(node.metadata);
       element.type = node.type?.type ?? _dynamicType;
     }
 
-    node.metadata.accept(this);
-    _setElementAnnotations(node.metadata, element.metadata);
+    _setOrCreateMetadataElements(element, node.metadata);
   }
 
   @override
@@ -985,21 +979,21 @@
 
     node.visitChildren(this);
 
-    List<ElementAnnotation> elementAnnotations;
+    NodeList<Annotation> annotations;
     if (parent is FieldDeclaration) {
-      elementAnnotations = _createElementAnnotations(parent.metadata);
+      annotations = parent.metadata;
     } else if (parent is TopLevelVariableDeclaration) {
-      elementAnnotations = _createElementAnnotations(parent.metadata);
+      annotations = parent.metadata;
     } else {
       // Local variable declaration
-      elementAnnotations = _createElementAnnotations(node.metadata);
+      annotations = node.metadata;
     }
 
     var variables = node.variables;
     for (var i = 0; i < variables.length; i++) {
       var variable = variables[i];
       var element = variable.declaredElement as ElementImpl;
-      element.metadata = elementAnnotations;
+      _setOrCreateMetadataElements(element, annotations);
 
       var offset = (i == 0 ? node.parent! : variable).offset;
       var length = variable.end - offset;
@@ -1092,11 +1086,11 @@
         element = TypeParameterElementImpl(name.name, name.offset);
         _elementHolder.addTypeParameter(element);
 
-        element.metadata = _createElementAnnotations(typeParameter.metadata);
         _setCodeRange(element, typeParameter);
       }
       name.staticElement = element;
       _define(element);
+      _setOrCreateMetadataElements(element, typeParameter.metadata);
     }
   }
 
@@ -1110,6 +1104,7 @@
     return annotations.map((annotation) {
       annotation as AnnotationImpl;
       var elementAnnotation = ElementAnnotationImpl(_unitElement);
+      elementAnnotation.annotationAst = annotation;
       annotation.elementAnnotation = elementAnnotation;
       return elementAnnotation;
     }).toList();
@@ -1247,6 +1242,18 @@
     element.setCodeRange(node.offset, node.length);
   }
 
+  void _setOrCreateMetadataElements(
+    ElementImpl element,
+    NodeList<Annotation> annotations,
+  ) {
+    annotations.accept(this);
+    if (_elementWalker != null) {
+      _setElementAnnotations(annotations, element.metadata);
+    } else {
+      element.metadata = _createElementAnnotations(annotations);
+    }
+  }
+
   /// Make the given [holder] be the current one while running [f].
   void _withElementHolder(ElementHolder holder, void Function() f) {
     var previousHolder = _elementHolder;
diff --git a/pkg/analyzer/lib/src/dart/resolver/variable_declaration_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/variable_declaration_resolver.dart
index 18889af..63d610e 100644
--- a/pkg/analyzer/lib/src/dart/resolver/variable_declaration_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/variable_declaration_resolver.dart
@@ -7,7 +7,6 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
-import 'package:analyzer/src/dart/constant/utilities.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/error/codes.dart';
@@ -21,7 +20,7 @@
   VariableDeclarationResolver({
     required ResolverVisitor resolver,
     required bool strictInference,
-  })   : _resolver = resolver,
+  })  : _resolver = resolver,
         _strictInference = strictInference;
 
   void resolve(VariableDeclarationImpl node) {
@@ -66,14 +65,12 @@
       _resolver.flowAnalysis?.flow?.lateInitializer_end();
     }
 
-    // Note: in addition to cloning the initializers for const variables, we
-    // have to clone the initializers for non-static final fields (because if
-    // they occur in a class with a const constructor, they will be needed to
-    // evaluate the const constructor).
-    if (element is ConstVariableElement) {
-      (element as ConstVariableElement).constantInitializer =
-          ConstantAstCloner().cloneNullableNode(initializer);
+    // Initializers of top-level variables and fields are already included
+    // into elements during linking.
+    if (element is ConstLocalVariableElementImpl) {
+      element.constantInitializer = initializer;
     }
+
     _resolver.checkForInvalidAssignment(node.name, initializer,
         whyNotPromoted: whyNotPromoted);
   }
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index cad7cf7..0dcc2f6 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -1383,8 +1383,7 @@
   /// Generate a hint for `noSuchMethod` methods that do nothing except of
   /// calling another `noSuchMethod` that is not defined by `Object`.
   ///
-  /// @return `true` if and only if a hint code is generated on the passed node
-  /// See [HintCode.UNNECESSARY_NO_SUCH_METHOD].
+  /// Return `true` if and only if a hint code is generated on the passed node.
   bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) {
     if (node.name.name != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
       return false;
@@ -1409,7 +1408,7 @@
     if (body is ExpressionFunctionBody) {
       if (isNonObjectNoSuchMethodInvocation(body.expression)) {
         _errorReporter.reportErrorForNode(
-            HintCode.UNNECESSARY_NO_SUCH_METHOD, node);
+            HintCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
         return true;
       }
     } else if (body is BlockFunctionBody) {
@@ -1419,7 +1418,7 @@
         if (returnStatement is ReturnStatement &&
             isNonObjectNoSuchMethodInvocation(returnStatement.expression)) {
           _errorReporter.reportErrorForNode(
-              HintCode.UNNECESSARY_NO_SUCH_METHOD, node);
+              HintCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
           return true;
         }
       }
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index ffd5ef7..805f0bc 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -20,6 +20,7 @@
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
 import 'package:analyzer/src/dart/element/member.dart'
     show ConstructorMember, Member;
@@ -55,7 +56,6 @@
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/error/dead_code_verifier.dart';
 import 'package:analyzer/src/error/nullable_dereference_verifier.dart';
-import 'package:analyzer/src/generated/constant.dart';
 import 'package:analyzer/src/generated/element_resolver.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/error_detection_helpers.dart';
@@ -1246,10 +1246,6 @@
     flowAnalysis!.topLevelDeclaration_exit();
     nullSafetyDeadCodeVerifier.flowEnd(node);
 
-    var constructor = node.declaredElement as ConstructorElementImpl;
-    constructor.constantInitializers =
-        _createCloner().cloneNodeList(node.initializers);
-
     _enclosingFunction = outerFunction;
   }
 
@@ -1314,12 +1310,9 @@
     super.visitDefaultFormalParameter(node);
     ParameterElement element = node.declaredElement!;
 
-    // Clone the ASTs for default formal parameters, so that we can use them
-    // during constant evaluation.
-    if (element is ConstVariableElement &&
-        !_hasSerializedConstantInitializer(element)) {
-      (element as ConstVariableElement).constantInitializer =
-          _createCloner().cloneNullableNode(node.defaultValue);
+    if (element is DefaultParameterElementImpl &&
+        !element.isParameterOfTopLevelFunction) {
+      element.constantInitializer = node.defaultValue;
     }
   }
 
@@ -2160,12 +2153,6 @@
     _yieldStatementResolver.resolve(node);
   }
 
-  /// Return a newly created cloner that can be used to clone constant
-  /// expressions.
-  ConstantAstCloner _createCloner() {
-    return ConstantAstCloner();
-  }
-
   /// Creates a union of `T | Future<T>`, unless `T` is already a
   /// future-union, in which case it simply returns `T`.
   DartType _createFutureOr(DartType type) {
@@ -2175,19 +2162,6 @@
     return typeProvider.futureOrType(type);
   }
 
-  /// Return `true` if the given [parameter] element of the AST being resolved
-  /// is resynthesized and is an API-level, not local, so has its initializer
-  /// serialized.
-  bool _hasSerializedConstantInitializer(ParameterElement parameter) {
-    var executable = parameter.enclosingElement;
-    if (executable is MethodElement ||
-        executable is FunctionElement &&
-            executable.enclosingElement is CompilationUnitElement) {
-      return true;
-    }
-    return false;
-  }
-
   void _inferArgumentTypesForInstanceCreate(
       covariant InstanceCreationExpressionImpl node) {
     var constructorName = node.constructorName;
diff --git a/pkg/analyzer/test/src/diagnostics/unnecessary_no_such_method_test.dart b/pkg/analyzer/test/src/diagnostics/unnecessary_no_such_method_test.dart
index 705ab75..bfc841d 100644
--- a/pkg/analyzer/test/src/diagnostics/unnecessary_no_such_method_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unnecessary_no_such_method_test.dart
@@ -27,7 +27,7 @@
   }
 }
 ''', [
-      error(HintCode.UNNECESSARY_NO_SUCH_METHOD, 87, 55),
+      error(HintCode.UNNECESSARY_NO_SUCH_METHOD, 87, 12),
     ]);
   }
 
@@ -70,7 +70,7 @@
   noSuchMethod(y) => super.noSuchMethod(y);
 }
 ''', [
-      error(HintCode.UNNECESSARY_NO_SUCH_METHOD, 87, 41),
+      error(HintCode.UNNECESSARY_NO_SUCH_METHOD, 87, 12),
     ]);
   }
 
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 50f5e74..12fc769 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -23,6 +23,18 @@
 /// client requests 500 items in a variablesRequest for a list.
 const maxToStringsPerEvaluation = 10;
 
+/// An expression that evaluates to the exception for the current thread.
+///
+/// In order to support some functionality like "Copy Value" in VS Code's
+/// Scopes/Variables window, each variable must have a valid "evaluateName" (an
+/// expression that evaluates to it). Since we show exceptions in there we use
+/// this magic value as an expression that maps to it.
+///
+/// This is not intended to be used by the user directly, although if they
+/// evaluate it as an expression and the current thread has an exception, it
+/// will work.
+const threadExceptionExpression = r'$_threadException';
+
 /// A base DAP Debug Adapter implementation for running and debugging Dart-based
 /// applications (including Flutter and Tests).
 ///
@@ -268,6 +280,94 @@
     sendResponse();
   }
 
+  /// evaluateRequest is called by the client to evaluate a string expression.
+  ///
+  /// This could come from the user typing into an input (for example VS Code's
+  /// Debug Console), automatic refresh of a Watch window, or called as part of
+  /// an operation like "Copy Value" for an item in the watch/variables window.
+  ///
+  /// If execution is not paused, the `frameId` will not be provided.
+  @override
+  Future<void> evaluateRequest(
+    Request request,
+    EvaluateArguments args,
+    void Function(EvaluateResponseBody) sendResponse,
+  ) async {
+    final frameId = args.frameId;
+    // TODO(dantup): Special handling for clipboard/watch (see Dart-Code DAP) to
+    // avoid wrapping strings in quotes, etc.
+
+    // If the frameId was supplied, it maps to an ID we provided from stored
+    // data so we need to look up the isolate + frame index for it.
+    ThreadInfo? thread;
+    int? frameIndex;
+    if (frameId != null) {
+      final data = _isolateManager.getStoredData(frameId);
+      if (data != null) {
+        thread = data.thread;
+        frameIndex = (data.data as vm.Frame).index;
+      }
+    }
+
+    if (thread == null || frameIndex == null) {
+      // TODO(dantup): Dart-Code evaluates these in the context of the rootLib
+      // rather than just not supporting it. Consider something similar (or
+      // better here).
+      throw UnimplementedError('Global evaluation not currently supported');
+    }
+
+    // The value in the constant `frameExceptionExpression` is used as a special
+    // expression that evaluates to the exception on the current thread. This
+    // allows us to construct evaluateNames that evaluate to the fields down the
+    // tree to support some of the debugger functionality (for example
+    // "Copy Value", which re-evaluates).
+    final expression = args.expression.trim();
+    final exceptionReference = thread.exceptionReference;
+    final isExceptionExpression = expression == threadExceptionExpression ||
+        expression.startsWith('$threadExceptionExpression.');
+
+    vm.Response? result;
+    if (exceptionReference != null && isExceptionExpression) {
+      result = await _evaluateExceptionExpression(
+        exceptionReference,
+        expression,
+        thread,
+      );
+    } else {
+      result = await vmService?.evaluateInFrame(
+        thread.isolate.id!,
+        frameIndex,
+        expression,
+        disableBreakpoints: true,
+      );
+    }
+
+    if (result is vm.ErrorRef) {
+      throw DebugAdapterException(result.message ?? '<error ref>');
+    } else if (result is vm.Sentinel) {
+      throw DebugAdapterException(result.valueAsString ?? '<collected>');
+    } else if (result is vm.InstanceRef) {
+      final resultString = await _converter.convertVmInstanceRefToDisplayString(
+        thread,
+        result,
+        allowCallingToString: true,
+      );
+      // TODO(dantup): We may need to store `expression` with this data
+      // to allow building nested evaluateNames.
+      final variablesReference =
+          _converter.isSimpleKind(result.kind) ? 0 : thread.storeData(result);
+
+      sendResponse(EvaluateResponseBody(
+        result: resultString,
+        variablesReference: variablesReference,
+      ));
+    } else {
+      throw DebugAdapterException(
+        'Unknown evaluation response type: ${result?.runtimeType}',
+      );
+    }
+  }
+
   /// [initializeRequest] is the first call from the client during
   /// initialization and allows exchanging capabilities and configuration
   /// between client and server.
@@ -662,7 +762,7 @@
         // TODO(dantup): evaluateName
         // should be built taking the parent into account, for ex. if
         // args.variablesReference == thread.exceptionReference then we need to
-        // use some sythensized variable name like $e.
+        // use some sythensized variable name like `frameExceptionExpression`.
         variables.addAll(await _converter.convertVmInstanceToVariablesList(
           thread,
           object,
@@ -683,6 +783,38 @@
     sendResponse(VariablesResponseBody(variables: variables));
   }
 
+  /// Handles evaluation of an expression that is (or begins with)
+  /// `threadExceptionExpression` which corresponds to the exception at the top
+  /// of [thread].
+  Future<vm.Response?> _evaluateExceptionExpression(
+    int exceptionReference,
+    String expression,
+    ThreadInfo thread,
+  ) async {
+    final exception = _isolateManager.getStoredData(exceptionReference)?.data
+        as vm.InstanceRef?;
+
+    if (exception == null) {
+      return null;
+    }
+
+    if (expression == threadExceptionExpression) {
+      return exception;
+    }
+
+    // Strip the prefix off since we'll evaluate against the exception
+    // by its ID.
+    final expressionWithoutExceptionExpression =
+        expression.substring(threadExceptionExpression.length + 1);
+
+    return vmService?.evaluate(
+      thread.isolate.id!,
+      exception.id!,
+      expressionWithoutExceptionExpression,
+      disableBreakpoints: true,
+    );
+  }
+
   void _handleDebugEvent(vm.Event event) {
     _isolateManager.handleEvent(event);
   }
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index cff12c2..e3c53db 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'exceptions.dart';
 import 'logging.dart';
 import 'protocol_common.dart';
 import 'protocol_generated.dart';
@@ -64,6 +65,12 @@
     void Function() sendResponse,
   );
 
+  Future<void> evaluateRequest(
+    Request request,
+    EvaluateArguments args,
+    void Function(EvaluateResponseBody) sendResponse,
+  );
+
   /// Calls [handler] for an incoming request, using [fromJson] to parse its
   /// arguments from the request.
   ///
@@ -114,7 +121,7 @@
         requestSeq: request.seq,
         seq: _sequence++,
         command: request.command,
-        message: '$e',
+        message: e is DebugAdapterException ? e.message : '$e',
         body: '$s',
       );
       _channel.sendResponse(response);
@@ -279,6 +286,8 @@
       handle(request, scopesRequest, ScopesArguments.fromJson);
     } else if (request.command == 'variables') {
       handle(request, variablesRequest, VariablesArguments.fromJson);
+    } else if (request.command == 'evaluate') {
+      handle(request, evaluateRequest, EvaluateArguments.fromJson);
     } else {
       final response = Response(
         success: false,
diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart
index 7291ce8..85ef5f8 100644
--- a/pkg/dds/lib/src/dap/protocol_converter.dart
+++ b/pkg/dds/lib/src/dap/protocol_converter.dart
@@ -106,7 +106,7 @@
     final associations = instance.associations;
     final fields = instance.fields;
 
-    if (_isSimpleKind(instance.kind)) {
+    if (isSimpleKind(instance.kind)) {
       // For simple kinds, just return a single variable with their value.
       return [
         await convertVmResponseToVariable(
@@ -235,7 +235,7 @@
       // For non-simple variables, store them and produce a new reference that
       // can be used to access their fields/items/associations.
       final variablesReference =
-          _isSimpleKind(response.kind) ? 0 : thread.storeData(response);
+          isSimpleKind(response.kind) ? 0 : thread.storeData(response);
 
       return dap.Variable(
         name: name ?? response.kind.toString(),
@@ -371,6 +371,17 @@
     }
   }
 
+  /// Whether [kind] is a simple kind, and does not need to be mapped to a variable.
+  bool isSimpleKind(String? kind) {
+    return kind == 'String' ||
+        kind == 'Bool' ||
+        kind == 'Int' ||
+        kind == 'Num' ||
+        kind == 'Double' ||
+        kind == 'Null' ||
+        kind == 'Closure';
+  }
+
   /// Invokes the toString() method on a [vm.InstanceRef] and converts the
   /// response to a user-friendly display string.
   ///
@@ -434,15 +445,4 @@
 
     return getterNames;
   }
-
-  /// Whether [kind] is a simple kind, and does not need to be mapped to a variable.
-  bool _isSimpleKind(String? kind) {
-    return kind == 'String' ||
-        kind == 'Bool' ||
-        kind == 'Int' ||
-        kind == 'Num' ||
-        kind == 'Double' ||
-        kind == 'Null' ||
-        kind == 'Closure';
-  }
 }
diff --git a/pkg/dds/test/dap/integration/debug_eval_test.dart b/pkg/dds/test/dap/integration/debug_eval_test.dart
new file mode 100644
index 0000000..00a4408
--- /dev/null
+++ b/pkg/dds/test/dap/integration/debug_eval_test.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2021, 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:dds/src/dap/adapters/dart.dart';
+import 'package:test/test.dart';
+
+import 'test_client.dart';
+import 'test_support.dart';
+
+main() {
+  testDap((dap) async {
+    group('debug mode evaluation', () {
+      test('evaluates expressions with simple results', () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  var a = 1;
+  var b = 2;
+  var c = 'test';
+  print('Hello!'); // BREAKPOINT
+}''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+        await client.expectTopFrameEvalResult(stop.threadId!, 'a', '1');
+        await client.expectTopFrameEvalResult(stop.threadId!, 'a * b', '2');
+        await client.expectTopFrameEvalResult(stop.threadId!, 'c', '"test"');
+      });
+
+      test('evaluates expressions with complex results', () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  print('Hello!'); // BREAKPOINT
+}''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+        final result = await client.expectTopFrameEvalResult(
+          stop.threadId!,
+          'DateTime(2000, 1, 1)',
+          'DateTime',
+        );
+
+        // Check we got a variablesReference that maps on to the fields.
+        expect(result.variablesReference, greaterThan(0));
+        await client.expectVariables(
+          result.variablesReference,
+          '''
+            isUtc: false
+          ''',
+        );
+      });
+
+      test(
+          'evaluates complex expressions expressions with evaluateToStringInDebugViews=true',
+          () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  print('Hello!'); // BREAKPOINT
+}''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        final stop = await client.hitBreakpoint(
+          testFile,
+          breakpointLine,
+          launch: () =>
+              client.launch(testFile.path, evaluateToStringInDebugViews: true),
+        );
+
+        await client.expectTopFrameEvalResult(
+          stop.threadId!,
+          'DateTime(2000, 1, 1)',
+          'DateTime (2000-01-01 00:00:00.000)',
+        );
+      });
+
+      test(
+          'evaluates $threadExceptionExpression to the threads exception (simple type)',
+          () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  throw 'my error';
+}''');
+
+        final stop = await client.hitException(testFile);
+
+        final result = await client.expectTopFrameEvalResult(
+          stop.threadId!,
+          threadExceptionExpression,
+          '"my error"',
+        );
+        expect(result.variablesReference, equals(0));
+      });
+
+      test(
+          'evaluates $threadExceptionExpression to the threads exception (complex type)',
+          () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  throw Exception('my error');
+}''');
+
+        final stop = await client.hitException(testFile);
+        final result = await client.expectTopFrameEvalResult(
+          stop.threadId!,
+          threadExceptionExpression,
+          '_Exception',
+        );
+        expect(result.variablesReference, greaterThan(0));
+      });
+
+      test(
+          'evaluates $threadExceptionExpression.x.y to x.y on the threads exception',
+          () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  throw Exception('12345');
+}
+    ''');
+
+        final stop = await client.hitException(testFile);
+        await client.expectTopFrameEvalResult(
+          stop.threadId!,
+          '$threadExceptionExpression.message.length',
+          '5',
+        );
+      });
+
+      test('can evaluate expressions in non-top frames', () async {
+        final client = dap.client;
+        final testFile = await dap.createTestFile(r'''
+void main(List<String> args) {
+  var a = 999;
+  foo();
+}
+
+void foo() {
+  var a = 111; // BREAKPOINT
+}''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+        final stack = await client.getValidStack(stop.threadId!,
+            startFrame: 0, numFrames: 2);
+        final secondFrameId = stack.stackFrames[1].id;
+
+        await client.expectEvalResult(secondFrameId, 'a', '999');
+      });
+
+      // These tests can be slow due to starting up the external server process.
+    }, timeout: Timeout.none);
+  });
+}
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index f6c2a78..f1fade0 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -79,6 +79,23 @@
 
   Future<Response> disconnect() => sendRequest(DisconnectArguments());
 
+  /// Sends an evaluate request for the given [expression], optionally for a
+  /// specific [frameId].
+  ///
+  /// Returns a Future that completes when the server returns a corresponding
+  /// response.
+  Future<Response> evaluate(
+    String expression, {
+    int? frameId,
+    String? context,
+  }) {
+    return sendRequest(EvaluateArguments(
+      expression: expression,
+      frameId: frameId,
+      context: context,
+    ));
+  }
+
   /// Returns a Future that completes with the next [event] event.
   Future<Event> event(String event) => _logIfSlow(
       'Event "$event"',
@@ -549,4 +566,35 @@
 
     return variables;
   }
+
+  /// Evalutes [expression] in the top frame of thread [threadId] and expects a
+  /// specific [expectedResult].
+  Future<EvaluateResponseBody> expectTopFrameEvalResult(
+    int threadId,
+    String expression,
+    String expectedResult,
+  ) async {
+    final stack = await getValidStack(threadId, startFrame: 0, numFrames: 1);
+    final topFrameId = stack.stackFrames.first.id;
+
+    return expectEvalResult(topFrameId, expression, expectedResult);
+  }
+
+  /// Evalutes [expression] in frame [frameId] and expects a specific
+  /// [expectedResult].
+  Future<EvaluateResponseBody> expectEvalResult(
+    int frameId,
+    String expression,
+    String expectedResult,
+  ) async {
+    final response = await evaluate(expression, frameId: frameId);
+    expect(response.success, isTrue);
+    expect(response.command, equals('evaluate'));
+    final body =
+        EvaluateResponseBody.fromJson(response.body as Map<String, Object?>);
+
+    expect(body.result, equals(expectedResult));
+
+    return body;
+  }
 }
diff --git a/tests/language/const_functions/const_functions_const_ctor_error_test.dart b/tests/language/const_functions/const_functions_const_ctor_error_test.dart
index e8aec9e..2f677bf 100644
--- a/tests/language/const_functions/const_functions_const_ctor_error_test.dart
+++ b/tests/language/const_functions/const_functions_const_ctor_error_test.dart
@@ -57,8 +57,9 @@
 const var4 = C();
 //           ^^^
 // [analyzer] COMPILE_TIME_ERROR.CONST_WITH_NON_CONST
-//           ^^^^^
+//           ^^^
 // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+//           ^^^^^
 // [cfe] Cannot invoke a non-'const' constructor where a const expression is expected.
 class C {
   int? x;
diff --git a/tools/VERSION b/tools/VERSION
index 2476849..f6c718f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 207
+PRERELEASE 208
 PRERELEASE_PATCH 0
\ No newline at end of file