[VM/CFE] Fix expression compilation with record types
Fixes https://github.com/dart-lang/sdk/issues/56859 TEST=pkg/vm_service/test/evaluate_with_record_{1,2}_test.dart + CFE test Change-Id: I4fdd2bc8e6c04fc59aaad28a192fe0b1833f000d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/388841 Commit-Queue: Jens Johansen <jensj@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
committed by
Commit Queue
parent
21a4425043
commit
54f1ee4067
@@ -9,7 +9,9 @@ import 'package:kernel/ast.dart'
|
||||
DynamicType,
|
||||
InterfaceType,
|
||||
Library,
|
||||
NamedType,
|
||||
Nullability,
|
||||
RecordType,
|
||||
TypeParameter;
|
||||
import 'package:kernel/library_index.dart' show LibraryIndex;
|
||||
|
||||
@@ -91,8 +93,8 @@ List<ParsedType> parseDefinitionTypes(List<String> definitionTypes) {
|
||||
int i = 0;
|
||||
List<ParsedType> argumentReceivers = [];
|
||||
while (i < definitionTypes.length) {
|
||||
String uriOrNullString = definitionTypes[i];
|
||||
if (uriOrNullString == "null") {
|
||||
String uriOrSpecialString = definitionTypes[i];
|
||||
if (uriOrSpecialString == "null") {
|
||||
if (argumentReceivers.isEmpty) {
|
||||
result.add(new ParsedType.nullType());
|
||||
} else {
|
||||
@@ -103,6 +105,30 @@ List<ParsedType> parseDefinitionTypes(List<String> definitionTypes) {
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
} else if (uriOrSpecialString == "record") {
|
||||
// Record.
|
||||
// We expect at least 4 elements: "record", nullability, num fields,
|
||||
// num positional fields.
|
||||
if (i + 4 > definitionTypes.length) throw "invalid input";
|
||||
int nullability = int.parse(definitionTypes[i + 1]);
|
||||
int numFields = int.parse(definitionTypes[i + 2]);
|
||||
int numPositionalFields = int.parse(definitionTypes[i + 3]);
|
||||
i += 4;
|
||||
|
||||
List<String?> fieldNames = new List<String?>.filled(numFields, null);
|
||||
for (int j = numPositionalFields; j < numFields; j++) {
|
||||
fieldNames[j] = definitionTypes[i++];
|
||||
}
|
||||
|
||||
ParsedType type = new ParsedType.record(nullability, fieldNames);
|
||||
if (argumentReceivers.isEmpty) {
|
||||
result.add(type);
|
||||
} else {
|
||||
argumentReceivers.removeLast().arguments!.add(type);
|
||||
}
|
||||
for (int j = 0; j < numFields; j++) {
|
||||
argumentReceivers.add(type);
|
||||
}
|
||||
} else {
|
||||
// We expect at least 4 elements: Uri, class name, nullability,
|
||||
// number of type arguments.
|
||||
@@ -110,7 +136,8 @@ List<ParsedType> parseDefinitionTypes(List<String> definitionTypes) {
|
||||
String className = definitionTypes[i + 1];
|
||||
int nullability = int.parse(definitionTypes[i + 2]);
|
||||
int typeArgumentsCount = int.parse(definitionTypes[i + 3]);
|
||||
ParsedType type = new ParsedType(uriOrNullString, className, nullability);
|
||||
ParsedType type =
|
||||
new ParsedType.interface(uriOrSpecialString, className, nullability);
|
||||
if (argumentReceivers.isEmpty) {
|
||||
result.add(type);
|
||||
} else {
|
||||
@@ -128,26 +155,44 @@ List<ParsedType> parseDefinitionTypes(List<String> definitionTypes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
enum ParsedTypeKind {
|
||||
Null,
|
||||
Interface,
|
||||
Record,
|
||||
}
|
||||
|
||||
// Coverage-ignore(suite): Not run.
|
||||
class ParsedType {
|
||||
final ParsedTypeKind type;
|
||||
final String? uri;
|
||||
final String? className;
|
||||
final int? nullability;
|
||||
final List<ParsedType>? arguments;
|
||||
final List<String?>? recordFieldNames;
|
||||
|
||||
bool get isNullType => uri == null;
|
||||
ParsedType.interface(this.uri, this.className, this.nullability)
|
||||
: type = ParsedTypeKind.Interface,
|
||||
arguments = [],
|
||||
recordFieldNames = null;
|
||||
|
||||
ParsedType(this.uri, this.className, this.nullability) : arguments = [];
|
||||
ParsedType.record(this.nullability, this.recordFieldNames)
|
||||
: type = ParsedTypeKind.Record,
|
||||
uri = null,
|
||||
className = null,
|
||||
arguments = [];
|
||||
|
||||
ParsedType.nullType()
|
||||
: uri = null,
|
||||
: type = ParsedTypeKind.Null,
|
||||
uri = null,
|
||||
className = null,
|
||||
nullability = null,
|
||||
arguments = null;
|
||||
arguments = null,
|
||||
recordFieldNames = null;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! ParsedType) return false;
|
||||
if (type != other.type) return false;
|
||||
if (uri != other.uri) return false;
|
||||
if (className != other.className) return false;
|
||||
if (nullability != other.nullability) return false;
|
||||
@@ -157,42 +202,81 @@ class ParsedType {
|
||||
if (arguments![i] != other.arguments![i]) return false;
|
||||
}
|
||||
}
|
||||
if (recordFieldNames?.length != other.recordFieldNames?.length) {
|
||||
return false;
|
||||
}
|
||||
if (recordFieldNames != null) {
|
||||
for (int i = 0; i < recordFieldNames!.length; i++) {
|
||||
if (recordFieldNames![i] != other.recordFieldNames![i]) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
if (isNullType) return 0;
|
||||
if (type == ParsedTypeKind.Null) return 0;
|
||||
int hash = 0x3fffffff & uri.hashCode;
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ className.hashCode));
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ nullability.hashCode));
|
||||
for (ParsedType argument in arguments!) {
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ argument.hashCode));
|
||||
}
|
||||
if (recordFieldNames != null) {
|
||||
for (String? name in recordFieldNames!) {
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ name.hashCode));
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (isNullType) return "null-type";
|
||||
return "$uri[$className] ($nullability) <$arguments>";
|
||||
switch (type) {
|
||||
case ParsedTypeKind.Null:
|
||||
return "null-type";
|
||||
case ParsedTypeKind.Interface:
|
||||
return "Record[$recordFieldNames] ($nullability) ($arguments)";
|
||||
case ParsedTypeKind.Record:
|
||||
if (arguments?.isEmpty ?? true) {
|
||||
return "$uri[$className] ($nullability)";
|
||||
}
|
||||
return "$uri[$className] ($nullability) <$arguments>";
|
||||
}
|
||||
}
|
||||
|
||||
DartType createDartType(LibraryIndex libraryIndex) {
|
||||
if (isNullType) return new DynamicType();
|
||||
Class? classNode = libraryIndex.tryGetClass(uri!, className!);
|
||||
if (classNode == null) return new DynamicType();
|
||||
switch (type) {
|
||||
case ParsedTypeKind.Null:
|
||||
return new DynamicType();
|
||||
case ParsedTypeKind.Record:
|
||||
List<DartType> positional = [];
|
||||
List<NamedType> named = [];
|
||||
for (int i = 0; i < arguments!.length; i++) {
|
||||
String? name = recordFieldNames![i];
|
||||
DartType type = arguments![i].createDartType(libraryIndex);
|
||||
if (name == null) {
|
||||
positional.add(type);
|
||||
} else {
|
||||
named.add(new NamedType(name, type));
|
||||
}
|
||||
}
|
||||
return new RecordType(positional, named, _getDartNullability());
|
||||
case ParsedTypeKind.Interface:
|
||||
Class? classNode = libraryIndex.tryGetClass(uri!, className!);
|
||||
if (classNode == null) return new DynamicType();
|
||||
|
||||
return new InterfaceType(
|
||||
classNode,
|
||||
_getDartNullability(),
|
||||
arguments
|
||||
?.map((e) => e.createDartType(libraryIndex))
|
||||
.toList(growable: false));
|
||||
return new InterfaceType(
|
||||
classNode,
|
||||
_getDartNullability(),
|
||||
arguments
|
||||
?.map((e) => e.createDartType(libraryIndex))
|
||||
.toList(growable: false));
|
||||
}
|
||||
}
|
||||
|
||||
Nullability _getDartNullability() {
|
||||
if (isNullType) throw "No nullability on the null type";
|
||||
if (type == ParsedTypeKind.Null) throw "No nullability on the null type";
|
||||
if (nullability == 0) return Nullability.nullable;
|
||||
if (nullability == 1) return Nullability.nonNullable;
|
||||
if (nullability == 2) return Nullability.legacy;
|
||||
@@ -206,9 +290,14 @@ Set<String> collectParsedTypeUris(List<ParsedType> parsedTypes) {
|
||||
List<ParsedType> workList = new List.from(parsedTypes);
|
||||
while (workList.isNotEmpty) {
|
||||
ParsedType type = workList.removeLast();
|
||||
if (type.isNullType) continue;
|
||||
result.add(type.uri!);
|
||||
workList.addAll(type.arguments!);
|
||||
if (type.arguments != null) workList.addAll(type.arguments!);
|
||||
switch (type.type) {
|
||||
case ParsedTypeKind.Null:
|
||||
case ParsedTypeKind.Record:
|
||||
continue;
|
||||
case ParsedTypeKind.Interface:
|
||||
result.add(type.uri!);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ void main() {
|
||||
"1",
|
||||
"0",
|
||||
]),
|
||||
[new ParsedType("dart:core", "_OneByteString", 1)]);
|
||||
[new ParsedType.interface("dart:core", "_OneByteString", 1)]);
|
||||
|
||||
// List<something it can't represent which thus becomes an explicit
|
||||
// dynamic/null>, kNonNullable.
|
||||
expect(parseDefinitionTypes(["dart:core", "List", "1", "1", "null"]), [
|
||||
new ParsedType("dart:core", "List", 1)
|
||||
new ParsedType.interface("dart:core", "List", 1)
|
||||
..arguments!.add(new ParsedType.nullType())
|
||||
]);
|
||||
|
||||
@@ -39,8 +39,8 @@ void main() {
|
||||
"0",
|
||||
]),
|
||||
[
|
||||
new ParsedType("dart:core", "_GrowableList", 1)
|
||||
..arguments!.add(new ParsedType("dart:core", "int", 1))
|
||||
new ParsedType.interface("dart:core", "_GrowableList", 1)
|
||||
..arguments!.add(new ParsedType.interface("dart:core", "int", 1))
|
||||
]);
|
||||
|
||||
// Map<int, int>, kNonNullable
|
||||
@@ -60,9 +60,9 @@ void main() {
|
||||
"0",
|
||||
]),
|
||||
[
|
||||
new ParsedType("dart:core", "Map", 1)
|
||||
..arguments!.add(new ParsedType("dart:core", "int", 1))
|
||||
..arguments!.add(new ParsedType("dart:core", "int", 1))
|
||||
new ParsedType.interface("dart:core", "Map", 1)
|
||||
..arguments!.add(new ParsedType.interface("dart:core", "int", 1))
|
||||
..arguments!.add(new ParsedType.interface("dart:core", "int", 1))
|
||||
]);
|
||||
|
||||
// [0] = String
|
||||
@@ -102,33 +102,52 @@ void main() {
|
||||
]),
|
||||
<ParsedType>[
|
||||
// String
|
||||
new ParsedType("dart:core", "_OneByteString", 1),
|
||||
new ParsedType.interface("dart:core", "_OneByteString", 1),
|
||||
// int
|
||||
new ParsedType("dart:core", "_Smi", 1),
|
||||
new ParsedType.interface("dart:core", "_Smi", 1),
|
||||
// List<String>
|
||||
new ParsedType("dart:core", "_GrowableList", 1)
|
||||
new ParsedType.interface("dart:core", "_GrowableList", 1)
|
||||
..arguments!.addAll([
|
||||
new ParsedType("dart:core", "String", 1),
|
||||
new ParsedType.interface("dart:core", "String", 1),
|
||||
]),
|
||||
// Bar
|
||||
new ParsedType("file://wherever/t.dart", "Bar", 1),
|
||||
new ParsedType.interface("file://wherever/t.dart", "Bar", 1),
|
||||
// null value
|
||||
new ParsedType.nullType(),
|
||||
// HashMap<Map<int, List<int>>, List<String>>
|
||||
new ParsedType("dart:collection", "_InternalLinkedHashMap", 1)
|
||||
new ParsedType.interface("dart:collection", "_InternalLinkedHashMap", 1)
|
||||
..arguments!.addAll([
|
||||
new ParsedType("dart:core", "Map", 1)
|
||||
new ParsedType.interface("dart:core", "Map", 1)
|
||||
..arguments!.addAll([
|
||||
new ParsedType("dart:core", "int", 1),
|
||||
new ParsedType("dart:core", "List", 1)
|
||||
new ParsedType.interface("dart:core", "int", 1),
|
||||
new ParsedType.interface("dart:core", "List", 1)
|
||||
..arguments!.addAll([
|
||||
new ParsedType("dart:core", "int", 1),
|
||||
new ParsedType.interface("dart:core", "int", 1),
|
||||
]),
|
||||
]),
|
||||
new ParsedType("dart:core", "List", 1)
|
||||
new ParsedType.interface("dart:core", "List", 1)
|
||||
..arguments!.addAll([
|
||||
new ParsedType("dart:core", "String", 1),
|
||||
new ParsedType.interface("dart:core", "String", 1),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
|
||||
// Set<(int, {int foo})>, kNonNullable
|
||||
expect(
|
||||
parseDefinitionTypes([
|
||||
//
|
||||
/**/ "dart:_compact_hash", "_Set", "1", "1",
|
||||
/* */ "record", "1", "2", "1", "foo",
|
||||
/* */ "dart:core", "int", "1", "0",
|
||||
/* */ "dart:core", "int", "1", "0",
|
||||
]),
|
||||
[
|
||||
new ParsedType.interface("dart:_compact_hash", "_Set", 1)
|
||||
..arguments!.add(
|
||||
new ParsedType.record(1, [null, "foo"])
|
||||
..arguments!.add(new ParsedType.interface("dart:core", "int", 1))
|
||||
..arguments!.add(new ParsedType.interface("dart:core", "int", 1)),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (c) 2024, 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.
|
||||
|
||||
sources: |
|
||||
void stopHere() {
|
||||
List<(int, {double foo, String bar})> listOfRecord = [(42, foo: 42.42, bar: "fortytwo")];
|
||||
print(helper(listOfRecord));
|
||||
}
|
||||
bool helper(List<(int, {double foo, String bar})> listOfRecord) {
|
||||
final record = listOfRecord.first;
|
||||
return record.$1 == 42 && record.foo >= 42.0 && record.bar.length >= 4;
|
||||
}
|
||||
definitions: ["listOfRecord"]
|
||||
# List<(int, {String bar, double foo})> // Note the names being sorted!
|
||||
definition_types: ["dart:core", "List", "1", "1", "record", "1", "3", "1", "bar", "foo", "dart:core", "int", "1", "0", "dart:core", "String", "1", "0", "dart:core", "double", "1", "0"]
|
||||
type_definitions: []
|
||||
type_bounds: []
|
||||
type_defaults: []
|
||||
method: "stopHere"
|
||||
expression: |
|
||||
helper(listOfRecord)
|
||||
@@ -0,0 +1,4 @@
|
||||
Errors: {
|
||||
}
|
||||
method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr(dart.core::List<(dart.core::int, {bar: dart.core::String, foo: dart.core::double})> listOfRecord) → dynamic
|
||||
return #lib1::helper(listOfRecord);
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2024, 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:developer';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void testFunction() {
|
||||
final listOfRecord = [(42, foo: 42.42, bar: 'fortytwo')];
|
||||
debugger();
|
||||
print(helper(listOfRecord));
|
||||
}
|
||||
|
||||
bool helper(List<(int, {double foo, String bar})> listOfRecord) {
|
||||
final record = listOfRecord.first;
|
||||
return record.$1 == 42 && record.foo >= 42.0 && record.bar.length >= 4;
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasStoppedAtBreakpoint,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final result = await service.evaluateInFrame(
|
||||
isolateRef.id!,
|
||||
0,
|
||||
'helper(listOfRecord)',
|
||||
) as InstanceRef;
|
||||
expect(result.valueAsString, equals('true'));
|
||||
expect(result.kind, equals(InstanceKind.kBool));
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'evaluate_with_record_1_test.dart',
|
||||
testeeConcurrent: testFunction,
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2024, 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:developer';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void testFunction() {
|
||||
final set = {for (var i = 0; i < 4; i++) (i, foo: 42.42, bar: 'x-')};
|
||||
print(takesSetOfRecord(set));
|
||||
}
|
||||
|
||||
String takesSetOfRecord(Set<(int, {double foo, String bar})> set) {
|
||||
debugger();
|
||||
return helper(set);
|
||||
}
|
||||
|
||||
String helper(Set<(int, {double foo, String bar})> set) {
|
||||
final int i = set.fold(0, (a, b) => a + b.$1);
|
||||
final double foo = set.fold(0, (a, b) => a + b.foo);
|
||||
final String bar = set.fold('', (a, b) => a + b.bar);
|
||||
return (i, foo: foo, bar: bar).toString();
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasStoppedAtBreakpoint,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final result = await service.evaluateInFrame(
|
||||
isolateRef.id!,
|
||||
0,
|
||||
'helper(set)',
|
||||
) as InstanceRef;
|
||||
expect(result.valueAsString, equals('(6, bar: x-x-x-x-, foo: 169.68)'));
|
||||
expect(result.kind, equals(InstanceKind.kString));
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'evaluate_with_record_2_test.dart',
|
||||
testeeConcurrent: testFunction,
|
||||
);
|
||||
+28
-1
@@ -2991,8 +2991,35 @@ static void CollectStringifiedType(Thread* thread,
|
||||
return;
|
||||
}
|
||||
if (type.IsRecordType()) {
|
||||
// _Record class is not useful for the CFE. We use null instead.
|
||||
const auto& record = RecordType::Cast(type);
|
||||
const intptr_t num_fields = record.NumFields();
|
||||
const Array& field_names =
|
||||
Array::Handle(zone, record.GetFieldNames(thread));
|
||||
const intptr_t num_positional_fields = num_fields - field_names.Length();
|
||||
|
||||
// Records have their own encoding:
|
||||
// "record" <nullability> <num fields> <num positional fields>
|
||||
// <field names> <encoding of field>
|
||||
instance ^= String::New("record");
|
||||
output.Add(instance);
|
||||
instance ^= Smi::New((intptr_t)type.nullability());
|
||||
output.Add(instance);
|
||||
instance ^= Smi::New(num_fields);
|
||||
output.Add(instance);
|
||||
instance ^= Smi::New(num_positional_fields);
|
||||
output.Add(instance);
|
||||
|
||||
String& name = String::Handle(zone);
|
||||
for (intptr_t i = 0, n = field_names.Length(); i < n; ++i) {
|
||||
name ^= field_names.At(i);
|
||||
output.Add(name);
|
||||
}
|
||||
|
||||
AbstractType& field_type = AbstractType::Handle(zone);
|
||||
for (intptr_t i = 0, n = num_fields; i < n; ++i) {
|
||||
field_type = record.FieldTypeAt(i);
|
||||
CollectStringifiedType(thread, zone, field_type, output);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type.IsDynamicType()) {
|
||||
|
||||
Reference in New Issue
Block a user