Skip to content

Commit 4a41cf9

Browse files
author
Anna Gringauze
authored
Support records (#1919)
* Display records and add tests * Add _experiment test fixture * Update instanceRef kind to kRecord * Make offsets work * Format * Require min versions of dds and vm_service that support records * Cleanup and add more records tests * Cleanup names * Fix lists and maps getObject tests * Fix records test failures * Temporarily disabled failing record type tests * cleanup comments and formatting * Enable rectord type tests on main * Addressed CR comments, added tests for named parameters, fixed bugs * Fix list count bug * Update min SDK constrain to support record types and enable skipped tests * Fix merge errors * Fix failure on getObject of list with out of range offset * Cleanup * Use JS logic to check for dart types * Use integers as BoundField names for records * Create positional fields faster, add comments * Cleaned up comments * Addressed comments * Address CR comments
1 parent d6229e3 commit 4a41cf9

File tree

24 files changed

+747
-65
lines changed

24 files changed

+747
-65
lines changed

Diff for: dwds/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
- Cleanup `getObject` code for lists and maps.
44
- Now works with offset `0` and `null` count.
55
- Fix failures on edge cases.
6+
- Support records:
7+
- Update SDK constraint to `>=3.0.0-188.0.dev <4.0.0`.
8+
- Update `package:vm_service` constraint to `>=10.1.2 <12.0.0`.
9+
- Update `package:dds` constraint to `^2.7.1`.
10+
- Fill `BoundField.name` for records.
11+
- Display records as a container of fields.
612

713
## 17.0.0
814

Diff for: dwds/debug_extension/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: >-
66
A chrome extension for Dart debugging.
77
88
environment:
9-
sdk: ">=3.0.0-134.0.dev <4.0.0"
9+
sdk: ">=3.0.0-188.0.dev <4.0.0"
1010

1111
dependencies:
1212
async: ^2.3.0

Diff for: dwds/debug_extension_mv3/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: >-
66
A Chrome extension for Dart debugging.
77
88
environment:
9-
sdk: ">=3.0.0-134.0.dev <4.0.0"
9+
sdk: ">=3.0.0-188.0.dev <4.0.0"
1010

1111
dependencies:
1212
built_value: ^8.3.0

Diff for: dwds/lib/src/debugging/instance.dart

+180-11
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ import 'package:dwds/src/utilities/conversions.dart';
1212
import 'package:dwds/src/utilities/domain.dart';
1313
import 'package:dwds/src/utilities/objects.dart';
1414
import 'package:dwds/src/utilities/shared.dart';
15+
import 'package:logging/logging.dart';
1516
import 'package:vm_service/vm_service.dart';
1617
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
1718

1819
/// Contains a set of methods for getting [Instance]s and [InstanceRef]s.
1920
class InstanceHelper extends Domain {
21+
final _logger = Logger('InstanceHelper');
22+
2023
InstanceHelper(AppInspectorInterface appInspector, this.debugger) {
2124
inspector = appInspector;
2225
}
@@ -112,7 +115,7 @@ class InstanceHelper extends Domain {
112115

113116
final classRef = metaData?.classRef;
114117
if (metaData == null || classRef == null) return null;
115-
if (metaData.jsName == 'Function') {
118+
if (metaData.isFunction) {
116119
return _closureInstanceFor(remoteObject);
117120
}
118121

@@ -122,6 +125,9 @@ class InstanceHelper extends Domain {
122125
} else if (metaData.isSystemMap) {
123126
return await _mapInstanceFor(classRef, remoteObject,
124127
offset: offset, count: count, length: metaData.length);
128+
} else if (metaData.isRecord) {
129+
return await _recordInstanceFor(classRef, remoteObject,
130+
offset: offset, count: count, length: metaData.length);
125131
} else {
126132
return await _plainInstanceFor(classRef, remoteObject,
127133
offset: offset, count: count, length: metaData.length);
@@ -150,6 +156,7 @@ class InstanceHelper extends Domain {
150156
Future<BoundField> _fieldFor(Property property, ClassRef classRef) async {
151157
final instance = await _instanceRefForRemote(property.value);
152158
return BoundField(
159+
name: property.name,
153160
decl: FieldRef(
154161
// TODO(grouma) - Convert JS name to Dart.
155162
name: property.name,
@@ -207,6 +214,12 @@ class InstanceHelper extends Domain {
207214
}
208215

209216
/// The associations for a Dart Map or IdentityMap.
217+
///
218+
/// Returns a range of [count] associations, if available, starting from
219+
/// the [offset].
220+
///
221+
/// If [offset] is `null`, assumes 0 offset.
222+
/// If [count] is `null`, return all fields starting from the offset.
210223
Future<List<MapAssociation>> _mapAssociations(RemoteObject map,
211224
{int? offset, int? count}) async {
212225
// We do this in in awkward way because we want the keys and values, but we
@@ -247,6 +260,14 @@ class InstanceHelper extends Domain {
247260
}
248261

249262
/// Create a Map instance with class [classRef] from [remoteObject].
263+
///
264+
/// Returns an instance containing [count] associations, if available,
265+
/// starting from the [offset].
266+
///
267+
/// If [offset] is `null`, assumes 0 offset.
268+
/// If [count] is `null`, return all fields starting from the offset.
269+
/// [length] is the expected length of the whole object, read from
270+
/// the [ClassMetaData].
250271
Future<Instance?> _mapInstanceFor(
251272
ClassRef classRef,
252273
RemoteObject remoteObject, {
@@ -273,8 +294,15 @@ class InstanceHelper extends Domain {
273294
..associations = associations;
274295
}
275296

276-
/// Create a List instance of [classRef] from [remoteObject] with the JS
277-
/// properties [properties].
297+
/// Create a List instance of [classRef] from [remoteObject].
298+
///
299+
/// Returns an instance containing [count] elements, if available,
300+
/// starting from the [offset].
301+
///
302+
/// If [offset] is `null`, assumes 0 offset.
303+
/// If [count] is `null`, return all fields starting from the offset.
304+
/// [length] is the expected length of the whole object, read from
305+
/// the [ClassMetaData].
278306
Future<Instance?> _listInstanceFor(
279307
ClassRef classRef,
280308
RemoteObject remoteObject, {
@@ -301,6 +329,14 @@ class InstanceHelper extends Domain {
301329
}
302330

303331
/// The elements for a Dart List.
332+
///
333+
/// Returns a range of [count] elements, if available, starting from
334+
/// the [offset].
335+
///
336+
/// If [offset] is `null`, assumes 0 offset.
337+
/// If [count] is `null`, return all fields starting from the offset.
338+
/// [length] is the expected length of the whole object, read from
339+
/// the [ClassMetaData].
304340
Future<List<InstanceRef?>> _listElements(
305341
RemoteObject list, {
306342
int? offset,
@@ -322,6 +358,139 @@ class InstanceHelper extends Domain {
322358
.map((element) async => await _instanceRefForRemote(element.value)));
323359
}
324360

361+
/// Return elements of the list from [properties].
362+
///
363+
/// Ignore any non-elements like 'length', 'fixed$length', etc.
364+
static List<Property> _indexedListProperties(List<Property> properties) =>
365+
properties
366+
.where((p) => p.name != null && int.tryParse(p.name!) != null)
367+
.toList();
368+
369+
/// The fields for a Dart Record.
370+
///
371+
/// Returns a range of [count] fields, if available, starting from
372+
/// the [offset].
373+
///
374+
/// If [offset] is `null`, assumes 0 offset.
375+
/// If [count] is `null`, return all fields starting from the offset.
376+
Future<List<BoundField>> _recordFields(RemoteObject map,
377+
{int? offset, int? count}) async {
378+
// We do this in in awkward way because we want the keys and values, but we
379+
// can't return things by value or some Dart objects will come back as
380+
// values that we need to be RemoteObject, e.g. a List of int.
381+
final expression = '''
382+
function() {
383+
var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
384+
var shape = sdkUtils.dloadRepl(this, "shape");
385+
var positionalCount = sdkUtils.dloadRepl(shape, "positionals");
386+
var named = sdkUtils.dloadRepl(shape, "named");
387+
named = named == null? null: sdkUtils.dsendRepl(named, "toList", []);
388+
var values = sdkUtils.dloadRepl(this, "values");
389+
values = sdkUtils.dsendRepl(values, "toList", []);
390+
391+
return {
392+
positionalCount: positionalCount,
393+
named: named,
394+
values: values
395+
};
396+
}
397+
''';
398+
final result = await inspector.jsCallFunctionOn(map, expression, []);
399+
final positionalCountObject =
400+
await inspector.loadField(result, 'positionalCount');
401+
if (positionalCountObject == null || positionalCountObject.value is! int) {
402+
_logger.warning(
403+
'Unexpected positional count from record: $positionalCountObject');
404+
return [];
405+
}
406+
407+
final namedObject = await inspector.loadField(result, 'named');
408+
final valuesObject = await inspector.loadField(result, 'values');
409+
410+
// Collect positional fields in the requested range.
411+
final positionalCount = positionalCountObject.value as int;
412+
final positionalOffset = offset ?? 0;
413+
final positionalAvailable =
414+
_remainingCount(positionalOffset, positionalCount);
415+
final positionalRangeCount =
416+
min(positionalAvailable, count ?? positionalAvailable);
417+
final positionalElements = [
418+
for (var i = positionalOffset + 1;
419+
i <= positionalOffset + positionalRangeCount;
420+
i++)
421+
i
422+
];
423+
424+
// Collect named fields in the requested range.
425+
// Account for already collected positional fields.
426+
final namedRangeOffset =
427+
offset == null ? null : _remainingCount(positionalCount, offset);
428+
final namedRangeCount =
429+
count == null ? null : _remainingCount(positionalRangeCount, count);
430+
final namedInstance = await instanceFor(namedObject,
431+
offset: namedRangeOffset, count: namedRangeCount);
432+
final namedElements =
433+
namedInstance?.elements?.map((e) => e.valueAsString) ?? [];
434+
435+
final fieldNameElements = [
436+
...positionalElements,
437+
...namedElements,
438+
];
439+
440+
final valuesInstance =
441+
await instanceFor(valuesObject, offset: offset, count: count);
442+
final valueElements = valuesInstance?.elements ?? [];
443+
444+
if (fieldNameElements.length != valueElements.length) {
445+
_logger.warning('Record fields and values are not the same length.');
446+
return [];
447+
}
448+
449+
final fields = <BoundField>[];
450+
Map.fromIterables(fieldNameElements, valueElements).forEach((key, value) {
451+
fields.add(BoundField(name: key, value: value));
452+
});
453+
return fields;
454+
}
455+
456+
static int _remainingCount(int collected, int requested) {
457+
return requested < collected ? 0 : requested - collected;
458+
}
459+
460+
/// Create a Record instance with class [classRef] from [remoteObject].
461+
///
462+
/// Returns an instance containing [count] fields, if available,
463+
/// starting from the [offset].
464+
///
465+
/// If [offset] is `null`, assumes 0 offset.
466+
/// If [count] is `null`, return all fields starting from the offset.
467+
/// [length] is the expected length of the whole object, read from
468+
/// the [ClassMetaData].
469+
Future<Instance?> _recordInstanceFor(
470+
ClassRef classRef,
471+
RemoteObject remoteObject, {
472+
int? offset,
473+
int? count,
474+
int? length,
475+
}) async {
476+
final objectId = remoteObject.objectId;
477+
if (objectId == null) return null;
478+
// Records are complicated, do an eval to get names and values.
479+
final fields =
480+
await _recordFields(remoteObject, offset: offset, count: count);
481+
final rangeCount = _calculateRangeCount(
482+
count: count, elementCount: fields.length, length: length);
483+
return Instance(
484+
identityHashCode: remoteObject.objectId.hashCode,
485+
kind: InstanceKind.kRecord,
486+
id: objectId,
487+
classRef: classRef)
488+
..length = length
489+
..offset = offset
490+
..count = rangeCount
491+
..fields = fields;
492+
}
493+
325494
/// Return the available count of elements in the requested range.
326495
/// Return `null` if the range includes the whole object.
327496
/// [count] is the range length requested by the `getObject` call.
@@ -336,14 +505,6 @@ class InstanceHelper extends Domain {
336505
return min(count, elementCount);
337506
}
338507

339-
/// Return elements of the list from [properties].
340-
///
341-
/// Ignore any non-elements like 'length', 'fixed$length', etc.
342-
static List<Property> _indexedListProperties(List<Property> properties) =>
343-
properties
344-
.where((p) => p.name != null && int.tryParse(p.name!) != null)
345-
.toList();
346-
347508
/// Filter [allJsProperties] and return a list containing only those
348509
/// that correspond to Dart fields on [remoteObject].
349510
///
@@ -461,6 +622,14 @@ class InstanceHelper extends Domain {
461622
classRef: metaData.classRef)
462623
..length = metaData.length;
463624
}
625+
if (metaData.isRecord) {
626+
return InstanceRef(
627+
kind: InstanceKind.kRecord,
628+
id: objectId,
629+
identityHashCode: remoteObject.objectId.hashCode,
630+
classRef: metaData.classRef)
631+
..length = metaData.length;
632+
}
464633
return InstanceRef(
465634
kind: InstanceKind.kPlainInstance,
466635
id: objectId,

0 commit comments

Comments
 (0)