diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart index 53cfc257268..1c53643b664 100644 --- a/pkg/dds/lib/src/dap/protocol_converter.dart +++ b/pkg/dds/lib/src/dap/protocol_converter.dart @@ -102,10 +102,12 @@ class ProtocolConverter { } } return stringValue; - } else if (_isList(ref)) { + } else if (ref.isList) { return '${ref.kind} (${ref.length} ${ref.length == 1 ? "item" : "items"})'; - } else if (_isMap(ref)) { + } else if (ref.isMap) { return 'Map (${ref.length} ${ref.length == 1 ? "item" : "items"})'; + } else if (ref.isSet) { + return 'Set (${ref.length} ${ref.length == 1 ? "item" : "items"})'; } else if (ref.kind == 'Type') { return 'Type (${ref.name})'; } else { @@ -145,14 +147,17 @@ class ProtocolConverter { ) ]; } else if (elements != null) { - // For lists, map each item (in the requested subset) to a variable. + // For lists and sets, map each item (in the requested subset) to a + // variable. // Elements can contain nulls! final start = startItem ?? 0; return Future.wait(elements.cast().mapIndexed( (index, response) { final name = '[${start + index}]'; + final nameForEvaluation = + instance.isSet ? '.elementAt(${start + index})' : name; final itemEvaluateName = - _adapter.combineEvaluateName(evaluateName, name); + _adapter.combineEvaluateName(evaluateName, nameForEvaluation); if (response is vm.InstanceRef) { _adapter.storeEvaluateName(response, itemEvaluateName); } @@ -209,7 +214,7 @@ class ProtocolConverter { variablesReference: thread.storeData(VariableData(mapEntry, format)), ); })); - } else if (_isList(instance) && + } else if (instance.isList && instance.length != null && instance.bytes != null) { final formatter = format ?? const VariableFormat(); @@ -518,7 +523,8 @@ class ProtocolConverter { allowCallingToString: allowCallingToString, format: format, ), - indexedVariables: _isList(response) ? response.length : null, + indexedVariables: + response.isList || response.isSet ? response.length : null, variablesReference: variablesReference, ); } else if (response is vm.Sentinel) { @@ -550,15 +556,6 @@ class ProtocolConverter { } } - /// Returns whether [ref] is a List kind. - /// - /// This includes standard Dart [List], as well as lists from - /// `dart:typed_data` such as `Uint8List`. - bool _isList(vm.InstanceRef ref) => ref.kind?.endsWith('List') ?? false; - - /// Returns whether [ref] is a Map kind. - bool _isMap(vm.InstanceRef ref) => ref.kind == 'Map'; - /// Converts a VM Service stack frame to a DAP stack frame. Future convertVmToDapStackFrame( ThreadInfo thread, @@ -820,3 +817,17 @@ class ProtocolConverter { return null; } } + +extension on vm.InstanceRef { + /// Whether this instance is a List kind. + /// + /// This includes standard Dart [List], as well as lists from + /// `dart:typed_data` such as `Uint8List`. + bool get isList => kind?.endsWith('List') ?? false; + + /// Whether this instance is a Map kind. + bool get isMap => kind == 'Map'; + + /// Whether this instance is a Set kind. + bool get isSet => kind == 'Set'; +} diff --git a/pkg/dds/test/dap/integration/debug_variables_test.dart b/pkg/dds/test/dap/integration/debug_variables_test.dart index 686fac31f34..47cf35571b0 100644 --- a/pkg/dds/test/dap/integration/debug_variables_test.dart +++ b/pkg/dds/test/dap/integration/debug_variables_test.dart @@ -405,6 +405,55 @@ void main(List args) { ); }); + test('renders a simple set', () async { + final client = dap.client; + final testFile = dap.createTestFile(''' +void main(List args) { + final myVariable = {"first", "second", "third", null}; + print('Hello!'); $breakpointMarker +} + '''); + final breakpointLine = lineWith(testFile, breakpointMarker); + + final stop = await client.hitBreakpoint(testFile, breakpointLine); + await client.expectLocalVariable( + stop.threadId!, + expectedName: 'myVariable', + expectedDisplayString: 'Set (4 items)', + expectedIndexedItems: 4, + expectedVariables: ''' + [0]: "first", eval: myVariable.elementAt(0) + [1]: "second", eval: myVariable.elementAt(1) + [2]: "third", eval: myVariable.elementAt(2) + [3]: null + ''', + ); + }); + + test('renders a simple set subset', () async { + final client = dap.client; + final testFile = dap.createTestFile(''' +void main(List args) { + final myVariable = {"first", "second", "third"}; + print('Hello!'); $breakpointMarker +} + '''); + final breakpointLine = lineWith(testFile, breakpointMarker); + + final stop = await client.hitBreakpoint(testFile, breakpointLine); + await client.expectLocalVariable( + stop.threadId!, + expectedName: 'myVariable', + expectedDisplayString: 'Set (3 items)', + expectedIndexedItems: 3, + expectedVariables: ''' + [1]: "second", eval: myVariable.elementAt(1) + ''', + start: 1, + count: 1, + ); + }); + test('only calls toString() for 100 items in a list', () async { final client = dap.client; // Generate a file that assigns a list of 150 items