blob: 27fa81a1b8039873c3b996b2107b5a27256cc710 [file] [log] [blame]
// Copyright (c) 2019, 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 'dart:math';
import 'package:dwds/src/config/tool_configuration.dart';
import 'package:dwds/src/debugging/inspector.dart';
import 'package:dwds/src/debugging/metadata/class.dart';
import 'package:dwds/src/debugging/metadata/function.dart';
import 'package:dwds/src/utilities/conversions.dart';
import 'package:dwds/src/utilities/domain.dart';
import 'package:dwds/src/utilities/objects.dart';
import 'package:dwds/src/utilities/shared.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
/// Contains a set of methods for getting [Instance]s and [InstanceRef]s.
class InstanceHelper extends Domain {
final _logger = Logger('InstanceHelper');
final ClassMetaDataHelper metadataHelper;
InstanceHelper(AppInspector appInspector)
: metadataHelper = ClassMetaDataHelper(appInspector) {
inspector = appInspector;
}
static final InstanceRef kNullInstanceRef =
_primitiveInstanceRef(InstanceKind.kNull, null);
/// Creates an [InstanceRef] for a primitive [RemoteObject].
static InstanceRef _primitiveInstanceRef(
String kind,
RemoteObject? remoteObject,
) {
final classRef = classRefFor('dart:core', kind);
return InstanceRef(
identityHashCode: dartIdFor(remoteObject?.value).hashCode,
kind: kind,
classRef: classRef,
id: dartIdFor(remoteObject?.value),
valueAsString: '${remoteObject?.value}',
);
}
/// Creates an [Instance] for a primitive [RemoteObject].
Instance? _primitiveInstance(String kind, RemoteObject? remoteObject) {
final objectId = remoteObject?.objectId;
if (objectId == null) return null;
return Instance(
identityHashCode: objectId.hashCode,
id: objectId,
kind: kind,
classRef: classRefFor('dart:core', kind),
valueAsString: '${remoteObject?.value}',
);
}
Instance? _stringInstanceFor(
RemoteObject? remoteObject,
int? offset,
int? count,
) {
// TODO(#777) Consider a way of not passing the whole string around (in the
// ID) in order to find a substring.
final objectId = remoteObject?.objectId;
if (objectId == null) return null;
final fullString = stringFromDartId(objectId);
var preview = fullString;
var truncated = false;
if (offset != null || count != null) {
truncated = true;
final start = offset ?? 0;
final end = count == null ? null : min(start + count, fullString.length);
preview = fullString.substring(start, end);
}
return Instance(
identityHashCode: createId().hashCode,
kind: InstanceKind.kString,
classRef: classRefForString,
id: createId(),
valueAsString: preview,
valueAsStringIsTruncated: truncated,
length: fullString.length,
count: (truncated ? preview.length : null),
offset: (truncated ? offset : null),
);
}
Instance? _closureInstanceFor(RemoteObject remoteObject) {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
final result = Instance(
kind: InstanceKind.kClosure,
id: objectId,
identityHashCode: remoteObject.objectId.hashCode,
classRef: classRefForClosure,
);
return result;
}
/// Create an [Instance] for the given [remoteObject].
///
/// Does a remote eval to get instance information. Returns null if there
/// isn't a corresponding instance. For enumerable objects, [offset] and
/// [count] allow retrieving a sub-range of properties.
Future<Instance?> instanceFor(
RemoteObject? remoteObject, {
int? offset,
int? count,
}) async {
final primitive = _primitiveInstanceOrNull(remoteObject, offset, count);
if (primitive != null) {
return primitive;
}
final objectId = remoteObject?.objectId;
if (remoteObject == null || objectId == null) return null;
final metaData = await metadataHelper.metaDataFor(remoteObject);
final classRef = metaData?.classRef;
if (metaData == null || classRef == null) return null;
switch (metaData.runtimeKind) {
case RuntimeObjectKind.function:
return _closureInstanceFor(remoteObject);
case RuntimeObjectKind.recordType:
return await _recordTypeInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
case RuntimeObjectKind.type:
return await _plainTypeInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
case RuntimeObjectKind.list:
return await _listInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
case RuntimeObjectKind.set:
return await _setInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
case RuntimeObjectKind.map:
return await _mapInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
case RuntimeObjectKind.record:
return await _recordInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
case RuntimeObjectKind.object:
case RuntimeObjectKind.nativeError:
case RuntimeObjectKind.nativeObject:
default:
return await _plainInstanceFor(
metaData,
remoteObject,
offset: offset,
count: count,
);
}
}
/// If [remoteObject] represents a primitive, return an [Instance] for it,
/// otherwise return null.
Instance? _primitiveInstanceOrNull(
RemoteObject? remoteObject,
int? offset,
int? count,
) {
switch (remoteObject?.type ?? 'undefined') {
case 'string':
return _stringInstanceFor(remoteObject, offset, count);
case 'number':
return _primitiveInstance(InstanceKind.kDouble, remoteObject);
case 'boolean':
return _primitiveInstance(InstanceKind.kBool, remoteObject);
case 'undefined':
return _primitiveInstance(InstanceKind.kNull, remoteObject);
default:
return null;
}
}
/// Create a bound field for [property] in an instance of [classRef].
Future<BoundField> _fieldFor(Property property, ClassRef classRef) async {
final instance = await _instanceRefForRemote(property.value);
// TODO(annagrin): convert JS name to dart and fill missing information.
//https://github.com/dart-lang/sdk/issues/46723
return BoundField(
name: property.name,
decl: FieldRef(
// TODO(grouma) - Convert JS name to Dart.
name: property.name,
declaredType: InstanceRef(
kind: InstanceKind.kType,
classRef: instance?.classRef,
identityHashCode: createId().hashCode,
id: createId(),
),
owner: classRef,
isConst: false,
isFinal: false,
isStatic: false,
id: createId(),
),
value: instance,
);
}
/// Create a plain instance of [classRef] from [remoteObject] and the JS
/// properties [properties].
Future<Instance?> _plainInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
final fields = await _getInstanceFields(
metaData,
remoteObject,
offset: offset,
count: count,
);
final result = Instance(
kind: InstanceKind.kPlainInstance,
id: objectId,
identityHashCode: remoteObject.objectId.hashCode,
classRef: metaData.classRef,
fields: fields,
);
return result;
}
Future<List<BoundField>> _getInstanceFields(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) throw StateError('Object id is null for instance');
final properties = await inspector.getProperties(
objectId,
offset: offset,
count: count,
length: metaData.length,
);
final dartProperties = await _dartFieldsFor(properties, remoteObject);
final boundFields = await Future.wait(
dartProperties
.map<Future<BoundField>>((p) => _fieldFor(p, metaData.classRef)),
);
return boundFields
.where((bv) => inspector.isDisplayableObject(bv.value))
.toList()
..sort(_compareBoundFields);
}
int _compareBoundFields(BoundField a, BoundField b) {
final aName = a.decl?.name;
final bName = b.decl?.name;
if (aName == null) return bName == null ? 0 : -1;
if (bName == null) return 1;
return aName.compareTo(bName);
}
/// The associations for a Dart Map or IdentityMap.
///
/// Returns a range of [count] associations, if available, starting from
/// the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
Future<List<MapAssociation>> _mapAssociations(
RemoteObject map, {
int? offset,
int? count,
}) async {
// We do this in in awkward way because we want the keys and values, but we
// can't return things by value or some Dart objects will come back as
// values that we need to be RemoteObject, e.g. a List of int.
final expression = _jsRuntimeFunctionCall('getMapElements(this)');
final keysAndValues = await inspector.jsCallFunctionOn(map, expression, []);
final keys = await inspector.loadField(keysAndValues, 'keys');
final values = await inspector.loadField(keysAndValues, 'values');
final keysInstance = await instanceFor(keys, offset: offset, count: count);
final valuesInstance =
await instanceFor(values, offset: offset, count: count);
final associations = <MapAssociation>[];
final keyElements = keysInstance?.elements;
final valueElements = valuesInstance?.elements;
if (keyElements != null && valueElements != null) {
Map.fromIterables(keyElements, valueElements).forEach((key, value) {
associations.add(MapAssociation(key: key, value: value));
});
}
return associations;
}
/// Create a Map instance with class [classRef] from [remoteObject].
///
/// Returns an instance containing [count] associations, if available,
/// starting from the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
Future<Instance?> _mapInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
// Maps are complicated, do an eval to get keys and values.
final associations =
await _mapAssociations(remoteObject, offset: offset, count: count);
final rangeCount = _calculateRangeCount(
count: count,
elementCount: associations.length,
length: metaData.length,
);
return Instance(
identityHashCode: remoteObject.objectId.hashCode,
kind: InstanceKind.kMap,
id: objectId,
classRef: metaData.classRef,
length: metaData.length,
offset: offset,
count: rangeCount,
associations: associations,
);
}
/// Create a List instance of [classRef] from [remoteObject].
///
/// Returns an instance containing [count] elements, if available,
/// starting from the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
Future<Instance?> _listInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
final elements = await _listElements(
remoteObject,
offset: offset,
count: count,
length: metaData.length,
);
final rangeCount = _calculateRangeCount(
count: count,
elementCount: elements.length,
length: metaData.length,
);
return Instance(
identityHashCode: remoteObject.objectId.hashCode,
kind: InstanceKind.kList,
id: objectId,
classRef: metaData.classRef,
length: metaData.length,
elements: elements,
offset: offset,
count: rangeCount,
);
}
/// The elements for a Dart List.
///
/// Returns a range of [count] elements, if available, starting from
/// the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
Future<List<InstanceRef?>> _listElements(
RemoteObject list, {
int? offset,
int? count,
int? length,
}) async {
final properties = await inspector.getProperties(
list.objectId!,
offset: offset,
count: count,
length: length,
);
// Filter out all non-indexed properties
final elements = _indexedListProperties(properties);
final rangeCount = _calculateRangeCount(
count: count,
elementCount: elements.length,
length: length,
);
final range = elements.sublist(0, rangeCount);
return Future.wait(
range.map((element) => _instanceRefForRemote(element.value)),
);
}
/// Return elements of the list from [properties].
///
/// Ignore any non-elements like 'length', 'fixed$length', etc.
static List<Property> _indexedListProperties(List<Property> properties) =>
properties
.where((p) => p.name != null && int.tryParse(p.name!) != null)
.toList();
/// The field names for a Dart record shape.
///
/// Returns a range of [count] fields, if available, starting from
/// the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
/// The [shape] object describes the shape using `positionalCount`
/// and `named` fields.
///
/// Returns list of field names for the record shape.
Future<List<dynamic>> _recordShapeFields(
RemoteObject shape, {
int? offset,
int? count,
}) async {
final positionalCountObject =
await inspector.loadField(shape, 'positionalCount');
if (positionalCountObject == null || positionalCountObject.value is! int) {
_logger.warning(
'Unexpected positional count from record: $positionalCountObject',
);
return [];
}
final namedObject = await inspector.loadField(shape, 'named');
final positionalCount = positionalCountObject.value as int;
final positionalOffset = offset ?? 0;
final positionalAvailable =
_remainingCount(positionalOffset, positionalCount);
final positionalRangeCount =
min(positionalAvailable, count ?? positionalAvailable);
final positionalElements = [
for (var i = positionalOffset + 1;
i <= positionalOffset + positionalRangeCount;
i++)
i,
];
// Collect named fields in the requested range.
// Account for already collected positional fields.
final namedRangeOffset =
offset == null ? null : _remainingCount(positionalCount, offset);
final namedRangeCount =
count == null ? null : _remainingCount(positionalRangeCount, count);
final namedInstance = await instanceFor(
namedObject,
offset: namedRangeOffset,
count: namedRangeCount,
);
final namedElements =
namedInstance?.elements?.map((e) => e.valueAsString) ?? [];
return [
...positionalElements,
...namedElements,
];
}
/// The fields for a Dart Record.
///
/// Returns a range of [count] fields, if available, starting from
/// the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
Future<List<BoundField>> _recordFields(
RemoteObject record, {
int? offset,
int? count,
}) async {
// We do this in in awkward way because we want the keys and values, but we
// can't return things by value or some Dart objects will come back as
// values that we need to be RemoteObject, e.g. a List of int.
final expression = _jsRuntimeFunctionCall('getRecordFields(this)');
final result = await inspector.jsCallFunctionOn(record, expression, []);
final fieldNameElements =
await _recordShapeFields(result, offset: offset, count: count);
final valuesObject = await inspector.loadField(result, 'values');
final valuesInstance =
await instanceFor(valuesObject, offset: offset, count: count);
final valueElements = valuesInstance?.elements ?? [];
return _elementsToBoundFields(fieldNameElements, valueElements);
}
/// Create a list of `BoundField`s from field [names] and [values].
List<BoundField> _elementsToBoundFields(
List<dynamic> names,
List<dynamic> values,
) {
if (names.length != values.length) {
_logger.warning('Bound field names and values are not the same length.');
return [];
}
final boundFields = <BoundField>[];
Map.fromIterables(names, values).forEach((name, value) {
boundFields.add(BoundField(name: name, value: value));
});
return boundFields;
}
static int _remainingCount(int collected, int requested) {
return requested < collected ? 0 : requested - collected;
}
/// Create a Record instance with class [classRef] from [remoteObject].
///
/// Returns an instance containing [count] fields, if available,
/// starting from the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
Future<Instance?> _recordInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
// Records are complicated, do an eval to get names and values.
final fields =
await _recordFields(remoteObject, offset: offset, count: count);
final rangeCount = _calculateRangeCount(
count: count,
elementCount: fields.length,
length: metaData.length,
);
return Instance(
identityHashCode: remoteObject.objectId.hashCode,
kind: InstanceKind.kRecord,
id: objectId,
classRef: metaData.classRef,
length: metaData.length,
offset: offset,
count: rangeCount,
fields: fields,
);
}
/// Create a RecordType instance with class [classRef] from [remoteObject].
///
/// Returns an instance containing [count] fields, if available,
/// starting from the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
Future<Instance?> _recordTypeInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
// Records are complicated, do an eval to get names and values.
final fields = await _recordTypeFields(
remoteObject,
offset: offset,
count: count,
);
final rangeCount = _calculateRangeCount(
count: count,
elementCount: fields.length,
length: metaData.length,
);
return Instance(
identityHashCode: remoteObject.objectId.hashCode,
kind: InstanceKind.kRecordType,
id: objectId,
classRef: metaData.classRef,
length: metaData.length,
offset: offset,
count: rangeCount,
fields: fields,
);
}
/// The field types for a Dart RecordType.
///
/// Returns a range of [count] field types, if available, starting from
/// the [offset].
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all field types starting from the offset.
Future<List<BoundField>> _recordTypeFields(
RemoteObject record, {
int? offset,
int? count,
}) async {
// We do this in in awkward way because we want the names and types, but we
// can't return things by value or some Dart objects will come back as
// values that we need to be RemoteObject, e.g. a List of int.
final expression = _jsRuntimeFunctionCall('getRecordTypeFields(this)');
final result = await inspector.jsCallFunctionOn(record, expression, []);
final fieldNameElements =
await _recordShapeFields(result, offset: offset, count: count);
final typesObject = await inspector.loadField(result, 'types');
final typesInstance =
await instanceFor(typesObject, offset: offset, count: count);
final typeElements = typesInstance?.elements ?? [];
return _elementsToBoundFields(fieldNameElements, typeElements);
}
Future<Instance?> _setInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final length = metaData.length;
final objectId = remoteObject.objectId;
if (objectId == null) return null;
final expression = _jsRuntimeFunctionCall('getSetElements(this)');
final result =
await inspector.jsCallFunctionOn(remoteObject, expression, []);
final entriesObject = await inspector.loadField(result, 'entries');
final entriesInstance =
await instanceFor(entriesObject, offset: offset, count: count);
final elements = entriesInstance?.elements ?? [];
final setInstance = Instance(
identityHashCode: remoteObject.objectId.hashCode,
kind: InstanceKind.kSet,
id: objectId,
classRef: metaData.classRef,
length: length,
elements: elements,
);
if (offset != null && offset > 0) {
setInstance.offset = offset;
}
if (length != null && elements.length < length) {
setInstance.count = elements.length;
}
return setInstance;
}
/// Create Type instance with class [classRef] from [remoteObject].
///
/// Collect information from the internal [remoteObject] and present
/// it as an instance of [Type] class.
///
/// Returns an instance containing `hashCode` and `runtimeType` fields.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
Future<Instance?> _plainTypeInstanceFor(
ClassMetaData metaData,
RemoteObject remoteObject, {
int? offset,
int? count,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;
final fields = await _getInstanceFields(
metaData,
remoteObject,
offset: offset,
count: count,
);
return Instance(
identityHashCode: objectId.hashCode,
kind: InstanceKind.kType,
id: objectId,
classRef: metaData.classRef,
name: metaData.typeName,
length: metaData.length,
offset: offset,
count: count,
fields: fields,
);
}
/// Return the available count of elements in the requested range.
/// Return `null` if the range includes the whole object.
/// [count] is the range length requested by the `getObject` call.
/// [elementCount] is the number of elements in the runtime object.
/// [length] is the expected length of the whole object, read from
/// the [ClassMetaData].
static int? _calculateRangeCount({
int? count,
int? elementCount,
int? length,
}) {
if (count == null) return null;
if (elementCount == null) return null;
if (length == elementCount) return null;
return min(count, elementCount);
}
/// Filter [allJsProperties] and return a list containing only those
/// that correspond to Dart fields on [remoteObject].
///
/// This only applies to objects with named fields, not Lists or Maps.
Future<List<Property>> _dartFieldsFor(
List<Property> allJsProperties,
RemoteObject remoteObject,
) async {
// An expression to find the field names from the types, extract both
// private (named by symbols) and public (named by strings) and return them
// as a comma-separated single string, so we can return it by value and not
// need to make multiple round trips.
//
// For maps and lists it's more complicated. Treat the actual SDK versions
// of these as special.
final fieldNameExpression =
_jsRuntimeFunctionCall('getObjectFieldNames(this)');
final result = await inspector.jsCallFunctionOn(
remoteObject,
fieldNameExpression,
[],
returnByValue: true,
);
final names = List<String>.from(result.value as List);
// TODO(#761): Better support for large collections.
return allJsProperties
.where((property) => names.contains(property.name))
.toList();
}
/// Create an InstanceRef for an object, which may be a RemoteObject, or may
/// be something returned by value from Chrome, e.g. number, boolean, or
/// String.
Future<InstanceRef?> instanceRefFor(Object value) {
final remote = value is RemoteObject
? value
: RemoteObject({'value': value, 'type': _chromeType(value)});
return _instanceRefForRemote(remote);
}
/// The Chrome type for a value.
String? _chromeType(Object? value) {
if (value == null) return null;
if (value is String) return 'string';
if (value is num) return 'number';
if (value is bool) return 'boolean';
if (value is Function) return 'function';
return 'object';
}
/// Create an [InstanceRef] for the given Chrome [remoteObject].
Future<InstanceRef?> _instanceRefForRemote(RemoteObject? remoteObject) async {
// If we have a null result, treat it as a reference to null.
if (remoteObject == null) {
return kNullInstanceRef;
}
switch (remoteObject.type) {
case 'string':
final stringValue = remoteObject.value as String?;
// TODO: Support truncation for long strings.
// TODO(#777): dartIdFor() will return an ID containing the entire
// string, even if we're truncating the string value here.
return InstanceRef(
identityHashCode: dartIdFor(remoteObject.value).hashCode,
id: dartIdFor(remoteObject.value),
classRef: classRefForString,
kind: InstanceKind.kString,
valueAsString: stringValue,
length: stringValue?.length,
);
case 'number':
return _primitiveInstanceRef(InstanceKind.kDouble, remoteObject);
case 'boolean':
return _primitiveInstanceRef(InstanceKind.kBool, remoteObject);
case 'undefined':
return _primitiveInstanceRef(InstanceKind.kNull, remoteObject);
case 'object':
final objectId = remoteObject.objectId;
if (objectId == null) {
return _primitiveInstanceRef(InstanceKind.kNull, remoteObject);
}
final metaData = await metadataHelper.metaDataFor(remoteObject);
if (metaData == null) return null;
return InstanceRef(
kind: metaData.kind,
id: objectId,
identityHashCode: objectId.hashCode,
classRef: metaData.classRef,
length: metaData.length,
name: metaData.typeName,
);
case 'function':
final objectId = remoteObject.objectId;
if (objectId == null) {
return _primitiveInstanceRef(InstanceKind.kNull, remoteObject);
}
final functionMetaData = await FunctionMetaData.metaDataFor(
inspector.remoteDebugger,
remoteObject,
);
// TODO(annagrin) - fill missing information.
// https://github.com/dart-lang/sdk/issues/46723
return InstanceRef(
kind: InstanceKind.kClosure,
id: objectId,
identityHashCode: objectId.hashCode,
classRef: classRefForClosure,
closureFunction: FuncRef(
name: functionMetaData.name,
id: createId(),
owner: classRefForUnknown,
isConst: false,
isStatic: false,
implicit: false,
isGetter: false,
isSetter: false,
),
closureContext: ContextRef(length: 0, id: createId()),
);
default:
// Return null for an unsupported type. This is likely a JS construct.
return null;
}
}
}
String _jsRuntimeFunctionCall(String expression) => '''
function() {
const sdk = ${globalToolConfiguration.loadStrategy.loadModuleSnippet}('dart_sdk');
const dart = sdk.dart;
return dart.$expression;
}
''';