Skip to content

Commit 8b42c95

Browse files
author
Anna Gringauze
authored
Cleanup record types display (#2070)
* Fix getObject failure on record class * Merge with master * Update PR reference * Cleanup record type display * Cleanup * Update changelog * Update version and build * Remove printing * Validate record and record type refs in tests * Address CR comments * Address CR comments * Address CR comments
1 parent 1cfb3bd commit 8b42c95

File tree

7 files changed

+946
-400
lines changed

7 files changed

+946
-400
lines changed

dwds/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Do not show async frame errors on evaluation. - [#2073](https://github.com/dart-lang/webdev/pull/2073)
44
- Refactor code for presenting record instances. - [#2074](https://github.com/dart-lang/webdev/pull/2074)
5+
- Display record types concisely. - [#2070](https://github.com/dart-lang/webdev/pull/2070)
56

67
## 19.0.0
78

dwds/lib/src/debugging/instance.dart

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ class InstanceHelper extends Domain {
155155
count: count,
156156
length: metaData.length,
157157
);
158+
} else if (metaData.isRecordType) {
159+
return await _recordTypeInstanceFor(
160+
classRef,
161+
remoteObject,
162+
offset: offset,
163+
count: count,
164+
length: metaData.length,
165+
);
158166
} else if (metaData.isSet) {
159167
return await _setInstanceFor(
160168
classRef,
@@ -416,16 +424,16 @@ class InstanceHelper extends Domain {
416424
int? count,
417425
int? length,
418426
}) async {
419-
// Filter out all non-indexed properties
420-
final elements = _indexedListProperties(
421-
await debugger.getProperties(
422-
list.objectId!,
423-
offset: offset,
424-
count: count,
425-
length: length,
426-
),
427+
final properties = await debugger.getProperties(
428+
list.objectId!,
429+
offset: offset,
430+
count: count,
431+
length: length,
427432
);
428433

434+
// Filter out all non-indexed properties
435+
final elements = _indexedListProperties(properties);
436+
429437
final rangeCount = _calculateRangeCount(
430438
count: count,
431439
elementCount: elements.length,
@@ -546,19 +554,19 @@ class InstanceHelper extends Domain {
546554
await instanceFor(valuesObject, offset: offset, count: count);
547555
final valueElements = valuesInstance?.elements ?? [];
548556

549-
if (fieldNameElements.length != valueElements.length) {
550-
_logger.warning('Record fields and values are not the same length.');
551-
return [];
552-
}
553-
554557
return _elementsToBoundFields(fieldNameElements, valueElements);
555558
}
556559

557560
/// Create a list of `BoundField`s from field [names] and [values].
558-
static List<BoundField> _elementsToBoundFields(
561+
List<BoundField> _elementsToBoundFields(
559562
List<dynamic> names,
560563
List<dynamic> values,
561564
) {
565+
if (names.length != values.length) {
566+
_logger.warning('Bound field names and values are not the same length.');
567+
return [];
568+
}
569+
562570
final boundFields = <BoundField>[];
563571
Map.fromIterables(names, values).forEach((name, value) {
564572
boundFields.add(BoundField(name: name, value: value));
@@ -608,6 +616,89 @@ class InstanceHelper extends Domain {
608616
..fields = fields;
609617
}
610618

619+
/// Create a RecordType instance with class [classRef] from [remoteObject].
620+
///
621+
/// Returns an instance containing [count] fields, if available,
622+
/// starting from the [offset].
623+
///
624+
/// If [offset] is `null`, assumes 0 offset.
625+
/// If [count] is `null`, return all fields starting from the offset.
626+
/// [length] is the expected length of the whole object, read from
627+
/// the [ClassMetaData].
628+
Future<Instance?> _recordTypeInstanceFor(
629+
ClassRef classRef,
630+
RemoteObject remoteObject, {
631+
int? offset,
632+
int? count,
633+
int? length,
634+
}) async {
635+
final objectId = remoteObject.objectId;
636+
if (objectId == null) return null;
637+
// Records are complicated, do an eval to get names and values.
638+
final fields =
639+
await _recordTypeFields(remoteObject, offset: offset, count: count);
640+
final rangeCount = _calculateRangeCount(
641+
count: count,
642+
elementCount: fields.length,
643+
length: length,
644+
);
645+
return Instance(
646+
identityHashCode: remoteObject.objectId.hashCode,
647+
kind: InstanceKind.kRecordType,
648+
id: objectId,
649+
classRef: classRef,
650+
)
651+
..length = length
652+
..offset = offset
653+
..count = rangeCount
654+
..fields = fields;
655+
}
656+
657+
/// The field types for a Dart RecordType.
658+
///
659+
/// Returns a range of [count] field types, if available, starting from
660+
/// the [offset].
661+
///
662+
/// If [offset] is `null`, assumes 0 offset.
663+
/// If [count] is `null`, return all field types starting from the offset.
664+
Future<List<BoundField>> _recordTypeFields(
665+
RemoteObject record, {
666+
int? offset,
667+
int? count,
668+
}) async {
669+
// We do this in in awkward way because we want the names and types, but we
670+
// can't return things by value or some Dart objects will come back as
671+
// values that we need to be RemoteObject, e.g. a List of int.
672+
final expression = '''
673+
function() {
674+
var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
675+
var shape = sdkUtils.dloadRepl(this, "shape");
676+
var positionalCount = sdkUtils.dloadRepl(shape, "positionals");
677+
var named = sdkUtils.dloadRepl(shape, "named");
678+
named = named == null? null: sdkUtils.dsendRepl(named, "toList", []);
679+
var types = sdkUtils.dloadRepl(this, "types");
680+
types = types.map(t => sdkUtils.wrapType(t));
681+
types = sdkUtils.dsendRepl(types, "toList", []);
682+
683+
return {
684+
positionalCount: positionalCount,
685+
named: named,
686+
types: types
687+
};
688+
}
689+
''';
690+
final result = await inspector.jsCallFunctionOn(record, expression, []);
691+
final fieldNameElements =
692+
await _recordShapeFields(result, offset: offset, count: count);
693+
694+
final typesObject = await inspector.loadField(result, 'types');
695+
final typesInstance =
696+
await instanceFor(typesObject, offset: offset, count: count);
697+
final typeElements = typesInstance?.elements ?? [];
698+
699+
return _elementsToBoundFields(fieldNameElements, typeElements);
700+
}
701+
611702
Future<Instance?> _setInstanceFor(
612703
ClassRef classRef,
613704
RemoteObject remoteObject, {
@@ -801,6 +892,14 @@ class InstanceHelper extends Domain {
801892
classRef: metaData.classRef,
802893
)..length = metaData.length;
803894
}
895+
if (metaData.isRecordType) {
896+
return InstanceRef(
897+
kind: InstanceKind.kRecordType,
898+
id: objectId,
899+
identityHashCode: remoteObject.objectId.hashCode,
900+
classRef: metaData.classRef,
901+
)..length = metaData.length;
902+
}
804903
if (metaData.isSet) {
805904
return InstanceRef(
806905
kind: InstanceKind.kSet,

dwds/lib/src/debugging/metadata/class.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:vm_service/vm_service.dart';
1010
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
1111

1212
const _dartCoreLibrary = 'dart:core';
13+
const _dartRuntimeLibrary = 'dart:_runtime';
1314
const _dartInterceptorsLibrary = 'dart:_interceptors';
1415

1516
/// A hard-coded ClassRef for the Closure class.
@@ -21,6 +22,10 @@ final classRefForString = classRefFor(_dartCoreLibrary, InstanceKind.kString);
2122
/// A hard-coded ClassRef for the Record class.
2223
final classRefForRecord = classRefFor(_dartCoreLibrary, InstanceKind.kRecord);
2324

25+
/// A hard-coded ClassRef for the RecordType class.
26+
final classRefForRecordType =
27+
classRefFor(_dartRuntimeLibrary, InstanceKind.kRecordType);
28+
2429
/// A hard-coded ClassRef for a (non-existent) class called Unknown.
2530
final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown');
2631

@@ -105,14 +110,21 @@ class ClassMetaData {
105110
Object? length,
106111
bool isFunction = false,
107112
bool isRecord = false,
113+
bool isRecordType = false,
108114
bool isNativeError = false,
109115
}) {
110116
final jName = jsName as String?;
111117
final dName = dartName as String?;
112118
final library = libraryId as String? ?? _dartCoreLibrary;
113119
final id = '$library:$jName';
114120

115-
final classRef = isRecord ? classRefForRecord : classRefFor(library, dName);
121+
var classRef = classRefFor(library, dName);
122+
if (isRecord) {
123+
classRef = classRefForRecord;
124+
}
125+
if (isRecordType) {
126+
classRef = classRefForRecordType;
127+
}
116128

117129
return ClassMetaData._(
118130
id,
@@ -122,6 +134,7 @@ class ClassMetaData {
122134
int.tryParse('$length'),
123135
isFunction,
124136
isRecord,
137+
isRecordType,
125138
isNativeError,
126139
);
127140
}
@@ -134,6 +147,7 @@ class ClassMetaData {
134147
this.length,
135148
this.isFunction,
136149
this.isRecord,
150+
this.isRecordType,
137151
this.isNativeError,
138152
);
139153

@@ -149,10 +163,12 @@ class ClassMetaData {
149163
function(arg) {
150164
const sdk = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk');
151165
const dart = sdk.dart;
166+
const core = sdk.core;
152167
const interceptors = sdk._interceptors;
153168
const classObject = dart.getReifiedType(arg);
154169
const isFunction = classObject instanceof dart.AbstractFunctionType;
155170
const isRecord = classObject instanceof dart.RecordType;
171+
const isRecordType = dart.is(arg, dart.RecordType);
156172
const isNativeError = dart.is(arg, interceptors.NativeError);
157173
const result = {};
158174
var name = isFunction ? 'Function' : classObject.name;
@@ -162,6 +178,7 @@ class ClassMetaData {
162178
result['dartName'] = dart.typeName(classObject);
163179
result['isFunction'] = isFunction;
164180
result['isRecord'] = isRecord;
181+
result['isRecordType'] = isRecordType;
165182
result['isNativeError'] = isNativeError;
166183
result['length'] = arg['length'];
167184
@@ -173,6 +190,10 @@ class ClassMetaData {
173190
result['length'] = positionalCount + namedCount;
174191
}
175192
193+
if (isRecordType) {
194+
result['name'] = 'RecordType';
195+
result['length'] = arg.types.length;
196+
}
176197
return result;
177198
}
178199
''';
@@ -190,6 +211,7 @@ class ClassMetaData {
190211
dartName: metadata['dartName'],
191212
isFunction: metadata['isFunction'],
192213
isRecord: metadata['isRecord'],
214+
isRecordType: metadata['isRecordType'],
193215
isNativeError: metadata['isNativeError'],
194216
length: metadata['length'],
195217
);
@@ -203,6 +225,8 @@ class ClassMetaData {
203225
}
204226
}
205227

228+
/// TODO(annagrin): convert fields and getters below to kinds.
229+
206230
/// True if this class refers to system maps, which are treated specially.
207231
///
208232
/// Classes that implement Map or inherit from MapBase are still treated as
@@ -219,9 +243,12 @@ class ClassMetaData {
219243
/// True if this class refers to a function type.
220244
bool isFunction;
221245

222-
/// True if this class refers to a record type.
246+
/// True if this class refers to a Record type.
223247
bool isRecord;
224248

249+
/// True if this class refers to a RecordType type.
250+
bool isRecordType;
251+
225252
/// True is this class refers to a native JS type.
226253
/// i.e. inherits from NativeError.
227254
bool isNativeError;

0 commit comments

Comments
 (0)