blob: 4df880cfb6afe00be42f2505fb51f3483ee87269 [file] [log] [blame]
// Copyright (c) 2022, 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 '../serialization/serialization.dart';
/// A canonicalized record shape comprising zero or more positional fields
/// followed by zero or more named fields in sorted order.
///
/// A RecordShape is used in conjunction with a simple [List] of length
/// [fieldCount] to represent data about a record, for example, the types or
/// values of the fields. Unnamed (indexed) fields correspond to the same index
/// in the List. Named fields follow the unnamed fields in the canonical
/// (sorted) order. [RecordShape.indexOfFieldName] can be used to find the index
/// corresponding to a name.
class RecordShape {
/// Tag used for identifying serialized [RecordShape] objects in a debugging
/// data stream.
static const String tag = 'record-shape';
/// Number of positional fields.
final int positionalFieldCount;
/// Names of named fields in canonical order.
final List<String> fieldNames;
int get namedFieldCount => fieldNames.length;
int get fieldCount => positionalFieldCount + namedFieldCount;
RecordShape._(this.positionalFieldCount, [this.fieldNames = const []]);
static String positionalFieldIndexToGetterName(int i) {
return '\$${i + 1}';
}
factory RecordShape(int positionalFieldCount, List<String> names) {
assert(positionalFieldCount >= 0);
if (names.isEmpty) {
if (0 <= positionalFieldCount && positionalFieldCount < _common.length) {
return _common[positionalFieldCount];
}
return RecordShape._(positionalFieldCount, const []);
}
assert(_isSorted(names));
return RecordShape._(positionalFieldCount, names);
}
static bool _isSorted(List<String> names) {
for (int i = 1; i < names.length; i++) {
if (names[i - 1].compareTo(names[i]) >= 0) return false;
}
return true;
}
static final List<RecordShape> _common = [
RecordShape._(0),
RecordShape._(1),
RecordShape._(2),
RecordShape._(3),
RecordShape._(4),
RecordShape._(5),
];
/// Deserializes a [RecordShape] object from [source].
factory RecordShape.readFromDataSource(DataSourceReader source) {
source.begin(tag);
int positionals = source.readInt();
List<String> names = source.readStrings();
source.end(tag);
return RecordShape(positionals, names);
}
/// Serializes this [RecordShape] to [sink].
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeInt(positionalFieldCount);
sink.writeStrings(fieldNames);
sink.end(tag);
}
/// Ordering for shapes. Shapes with fewer fields sort before shapes with more
/// fields. Shapes with the same number of fields are ordered
/// lexicographically, with unnamed fields coming before named fields.
static int compare(RecordShape a, RecordShape b) {
// Group by total field count, smaller shapes first.
int r = a.fieldCount.compareTo(b.fieldCount);
if (r != 0) return r;
final aNames = a.fieldNames;
final bNames = b.fieldNames;
r = aNames.length.compareTo(bNames.length);
if (r != 0) return r;
for (int i = 0; i < aNames.length; i++) {
r = aNames[i].compareTo(bNames[i]);
if (r != 0) return r;
}
assert(a == b);
return 0;
}
int indexOfFieldName(String name) {
int nameIndex = fieldNames.indexOf(name);
if (nameIndex < 0) throw ArgumentError.value(name, 'name');
return positionalFieldCount + nameIndex;
}
int indexOfGetterName(String name) {
int nameIndex = fieldNames.indexOf(name);
if (nameIndex < 0) {
if (name[0] == '\$') {
final position = int.tryParse(name.substring(1));
if (position != null && position <= positionalFieldCount) {
return position - 1;
}
}
return -1;
}
return positionalFieldCount + nameIndex;
}
String getterNameOfIndex(int index) =>
index < positionalFieldCount
? positionalFieldIndexToGetterName(index)
: fieldNames[index - positionalFieldCount];
bool nameMatchesGetter(String name) {
return indexOfGetterName(name) >= 0;
}
@override
bool operator ==(Object other) =>
identical(this, other) || other is RecordShape && _sameShape(this, other);
@override
late final int hashCode = _hashCode();
int _hashCode() {
return Object.hash(positionalFieldCount, Object.hashAll(fieldNames));
}
static bool _sameShape(RecordShape a, RecordShape b) {
if (a.positionalFieldCount != b.positionalFieldCount) return false;
if (a.fieldNames.length != b.fieldNames.length) return false;
for (int i = 0; i < a.fieldNames.length; i++) {
if (a.fieldNames[i] != b.fieldNames[i]) return false;
}
return true;
}
@override
String toString() {
if (fieldNames.isEmpty) {
return 'RecordShape($positionalFieldCount)';
}
return 'RecordShape($positionalFieldCount, {${fieldNames.join(", ")}})';
}
}