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