blob: fc4bd877df420e628af397e91fa9605ab79ee104 [file]
// 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:math';
import 'package:meta/meta.dart';
import 'canonicalization_context.dart';
import 'definition.dart';
import 'helper.dart';
import 'serialization_context.dart';
import 'syntax.g.dart';
/// A value recorded during compilation.
sealed class MaybeConstant {
const MaybeConstant();
/// The maximum depth of the constant tree.
///
/// Used for fast-path optimization in `operator ==`. Not included in
/// `hashCode` because it is implicitly covered by the content-based hash.
int get _depth;
/// The total number of nodes in the constant tree.
///
/// Used for fast-path optimization in `operator ==`. Not included in
/// `hashCode` because it is implicitly covered by the content-based hash.
int get _size;
/// Canonicalizes this [MaybeConstant].
MaybeConstant _canonicalizeChildren(CanonicalizationContext context);
/// Returns a new [MaybeConstant] that only contains information allowed
/// by the provided criteria.
///
/// If [definitionPackageName] is provided, constants that are instances of
/// classes or enums from other packages are replaced with an
/// [UnsupportedConstant].
MaybeConstant _filter({String? definitionPackageName});
/// Compares this [MaybeConstant] with [other] for stable sorting.
int _compareTo(MaybeConstant other) {
if (identical(this, other)) return 0;
// By comparing the depth first, the serialization order of constants
// always has the children serialized before the parents.
var compare = _depth.compareTo(other._depth);
if (compare != 0) return compare;
compare = _orderingTypePriority.compareTo(other._orderingTypePriority);
if (compare != 0) return compare;
compare = _size.compareTo(other._size);
if (compare != 0) return compare;
return _compareToSameType(other);
}
/// Internal comparison for objects of the same type.
@protected
int _compareToSameType(covariant MaybeConstant other);
/// A stable priority for this type.
@protected
int get _orderingTypePriority;
/// Compares this [MaybeConstant] with [other] for semantic equality.
///
/// If [allowPromotionOfUnsupported] is true, an [UnsupportedConstant] in
/// [other] matches any [Constant] in this.
@visibleForTesting
bool semanticEquals(
MaybeConstant other, {
bool allowPromotionOfUnsupported = false,
});
/// Converts this [MaybeConstant] object to a syntax representation.
ConstantSyntax _toSyntax(SerializationContext context);
/// Creates a [MaybeConstant] object from its syntax representation.
static MaybeConstant _fromSyntax(
ConstantSyntax syntax,
DeserializationContext context,
) => switch (syntax) {
NonConstantConstantSyntax() => const NonConstant(),
NullConstantSyntax() => const NullConstant(),
BoolConstantSyntax(:final value) => BoolConstant(value),
IntConstantSyntax(:final value) => IntConstant(value),
DoubleConstantSyntax(value: final doubleValue) => DoubleConstant(
switch (doubleValue.type) {
'number' => doubleValue.asNumberDoubleConstantValue.value!,
'positive_infinity' => double.infinity,
'negative_infinity' => double.negativeInfinity,
'not_a_number' => double.nan,
_ => throw FormatException(
'Invalid double constant type: ${doubleValue.type}',
),
},
),
StringConstantSyntax(:final value) => StringConstant(value),
SymbolConstantSyntax(:final name, :final libraryUri) => SymbolConstant(
name,
libraryUri: libraryUri,
),
ListConstantSyntax(:final value) => ListConstant(
value.map((i) {
final constant = context.constants[i];
if (constant is! Constant) {
throw FormatException(
'List constant element at index $i is not a constant',
);
}
return constant;
}).toList(),
),
SetConstantSyntax(:final value) => SetConstant(
value.map((i) {
final constant = context.constants[i];
if (constant is! Constant) {
throw FormatException(
'Set constant element at index $i is not a constant',
);
}
return constant;
}).toList(),
),
MapConstantSyntax(:final value) => MapConstant(
value.map(
(e) {
final key = context.constants[e.key];
if (key is! Constant) {
throw FormatException(
'Map constant key at index ${e.key} is not a constant',
);
}
final value = context.constants[e.value];
if (value is! Constant) {
throw FormatException(
'Map constant value at index ${e.value} is not a constant',
);
}
return MapEntry(key, value);
},
).toList(),
),
InstanceConstantSyntax(value: final value, :final definitionIndex) =>
InstanceConstant(
definition: context.definitions[definitionIndex],
fields: (value?.json ?? {}).map(
(key, index) {
final constant = context.constants[index as int];
if (constant is! Constant) {
throw FormatException(
'Instance constant field $key at index $index is not '
'a constant',
);
}
return MapEntry(key, constant);
},
),
),
EnumConstantSyntax(
value: final value,
:final definitionIndex,
:final index,
:final name,
) =>
EnumConstant(
definition: context.definitions[definitionIndex],
index: index,
name: name,
fields: (value ?? {}).map(
(key, index) {
final constant = context.constants[index];
if (constant is! Constant) {
throw FormatException(
'Enum constant field $key at index $index is not '
'a constant',
);
}
return MapEntry(key, constant);
},
),
),
RecordConstantSyntax(:final positional, :final named) => RecordConstant(
positional: (positional ?? const []).map((i) {
final constant = context.constants[i];
if (constant is! Constant) {
throw FormatException(
'Record constant positional field at index $i is not '
'a constant',
);
}
return constant;
}).toList(),
named: (named ?? const {}).map(
(key, index) {
final constant = context.constants[index];
if (constant is! Constant) {
throw FormatException(
'Record constant named field $key at index $index is not '
'a constant',
);
}
return MapEntry(key, constant);
},
),
),
UnsupportedConstantSyntax(:final message) => UnsupportedConstant(message),
_ => throw UnimplementedError(
'"${syntax.type}" is not a supported constant type',
),
};
}
/// A value that is not a constant.
final class NonConstant extends MaybeConstant {
/// Creates a [NonConstant] object representing a non-constant expression.
const NonConstant();
@override
NonConstantConstantSyntax _toSyntax(SerializationContext context) =>
NonConstantConstantSyntax();
@override
bool operator ==(Object other) => other is NonConstant;
@override
int get _depth => 1;
@override
int get _size => 1;
@override
MaybeConstant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
MaybeConstant _filter({String? definitionPackageName}) => this;
@override
int get hashCode => 0x4e6f6e43;
@override
int get _orderingTypePriority => 0;
@override
int _compareToSameType(NonConstant other) => 0;
@override
String toString() => 'NonConstant()';
@override
@visibleForTesting
bool semanticEquals(
MaybeConstant other, {
bool allowPromotionOfUnsupported = false,
}) => other is NonConstant;
}
/// A constant value that can be recorded and serialized.
///
/// This follows the AST constant concept from the Dart SDK.
///
/// This class is intentionally not sealed. Adding new subtypes of [Constant]
/// should not be a breaking change for users of this package. Users should
/// use a wildcard pattern or a default case when switching over [Constant].
abstract class Constant extends MaybeConstant {
/// Creates a [Constant] object.
const Constant();
@override
Constant _canonicalizeChildren(CanonicalizationContext context);
@override
Constant _filter({String? definitionPackageName});
@override
@visibleForTesting
bool semanticEquals(
MaybeConstant other, {
bool allowPromotionOfUnsupported = false,
}) {
if (this == other) return true;
if (allowPromotionOfUnsupported && other is UnsupportedConstant) {
return true;
}
return _semanticEqualsInternal(other, allowPromotionOfUnsupported);
}
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
);
}
/// The `null` constant value.
final class NullConstant extends Constant {
/// Creates a [NullConstant] object.
const NullConstant() : super();
@override
NullConstantSyntax _toSyntax(SerializationContext context) =>
NullConstantSyntax();
@override
bool operator ==(Object other) => other is NullConstant;
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
int get hashCode => 0x4e756c6c;
@override
int get _orderingTypePriority => 2;
@override
int _compareToSameType(NullConstant other) => 0;
@override
String toString() => 'NullConstant()';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) => other is NullConstant;
}
/// A constant value in Dart but not supported in `package:record_use`.
final class UnsupportedConstant extends Constant {
/// The reason why this constant is unsupported.
final String message;
/// Creates an [UnsupportedConstant] object with the given [message].
const UnsupportedConstant(this.message);
@override
UnsupportedConstantSyntax _toSyntax(SerializationContext context) =>
UnsupportedConstantSyntax(message: message);
@override
bool operator ==(Object other) =>
other is UnsupportedConstant && other.message == message;
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
int get hashCode => message.hashCode;
@override
int get _orderingTypePriority => 1;
@override
int _compareToSameType(UnsupportedConstant other) =>
message.compareTo(other.message);
@override
String toString() => 'UnsupportedConstant($message)';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) => other is UnsupportedConstant && other.message == message;
}
/// A constant boolean value.
final class BoolConstant extends Constant {
/// The underlying value of this constant.
final bool value;
/// Creates a [BoolConstant] object with the given boolean [value].
// ignore: avoid_positional_boolean_parameters
const BoolConstant(this.value);
@override
BoolConstantSyntax _toSyntax(SerializationContext context) =>
BoolConstantSyntax(value: value);
@override
int get hashCode => value.hashCode;
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
bool operator ==(Object other) =>
other is BoolConstant && other.value == value;
@override
int get _orderingTypePriority => 3;
@override
int _compareToSameType(BoolConstant other) {
if (value == other.value) return 0;
return value ? 1 : -1;
}
@override
String toString() => 'BoolConstant($value)';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) => other is BoolConstant && other.value == value;
}
/// A constant integer value.
final class IntConstant extends Constant {
/// The underlying value of this constant.
final int value;
/// Creates an [IntConstant] object with the given integer [value].
const IntConstant(this.value);
@override
IntConstantSyntax _toSyntax(SerializationContext context) =>
IntConstantSyntax(value: value);
@override
int get hashCode => value.hashCode;
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
bool operator ==(Object other) =>
other is IntConstant && other.value == value;
@override
int get _orderingTypePriority => 4;
@override
int _compareToSameType(IntConstant other) => value.compareTo(other.value);
@override
String toString() => 'IntConstant($value)';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) => other is IntConstant && other.value == value;
}
/// A constant double value.
final class DoubleConstant extends Constant {
/// The underlying value of this constant.
final double value;
/// Creates a [DoubleConstant] object with the given double [value].
const DoubleConstant(this.value);
@override
DoubleConstantSyntax _toSyntax(SerializationContext context) {
final DoubleConstantValueSyntax syntaxValue;
if (value.isNaN) {
syntaxValue = NotANumberDoubleConstantValueSyntax();
} else if (value == double.infinity) {
syntaxValue = PositiveInfinityDoubleConstantValueSyntax();
} else if (value == double.negativeInfinity) {
syntaxValue = NegativeInfinityDoubleConstantValueSyntax();
} else {
syntaxValue = NumberDoubleConstantValueSyntax(value: value);
}
return DoubleConstantSyntax(value: syntaxValue);
}
@override
int get hashCode => Object.hash(value, value.isNegative);
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
bool operator ==(Object other) =>
other is DoubleConstant && value.compareTo(other.value) == 0;
@override
int get _orderingTypePriority => 5;
@override
int _compareToSameType(DoubleConstant other) => value.compareTo(other.value);
@override
String toString() => 'DoubleConstant($value)';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) => other is DoubleConstant && value.compareTo(other.value) == 0;
}
/// A constant string value.
final class StringConstant extends Constant {
/// The underlying value of this constant.
final String value;
/// Creates a [StringConstant] object with the given string [value].
const StringConstant(this.value);
@override
StringConstantSyntax _toSyntax(SerializationContext context) =>
StringConstantSyntax(value: value);
@override
int get hashCode => value.hashCode;
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
bool operator ==(Object other) =>
other is StringConstant && other.value == value;
@override
int get _orderingTypePriority => 6;
@override
int _compareToSameType(StringConstant other) => value.compareTo(other.value);
@override
String toString() => 'StringConstant($value)';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) => other is StringConstant && other.value == value;
}
/// A constant symbol value.
final class SymbolConstant extends Constant {
/// The name of the symbol.
final String name;
/// The library URI if this is a private symbol (starts with '_').
/// Null for public symbols.
final String? libraryUri;
/// Creates a [SymbolConstant] object with the given [name] and optional
/// [libraryUri].
const SymbolConstant(this.name, {this.libraryUri});
@override
SymbolConstantSyntax _toSyntax(SerializationContext context) =>
SymbolConstantSyntax(name: name, libraryUri: libraryUri);
@override
int get hashCode => Object.hash(name, libraryUri);
@override
int get _depth => 1;
@override
int get _size => 1;
@override
Constant _canonicalizeChildren(CanonicalizationContext context) => this;
@override
Constant _filter({String? definitionPackageName}) => this;
@override
bool operator ==(Object other) =>
other is SymbolConstant &&
other.name == name &&
other.libraryUri == libraryUri;
@override
int get _orderingTypePriority => 7;
@override
int _compareToSameType(SymbolConstant other) {
final nameCompare = name.compareTo(other.name);
if (nameCompare != 0) return nameCompare;
if (libraryUri == null) return other.libraryUri == null ? 0 : -1;
if (other.libraryUri == null) return 1;
return libraryUri!.compareTo(other.libraryUri!);
}
@override
String toString() {
if (libraryUri == null) {
return '#$name';
}
return '$libraryUri::#$name';
}
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) =>
other is SymbolConstant &&
other.name == name &&
other.libraryUri == libraryUri;
}
/// A constant list of [Constant] values.
final class ListConstant extends Constant {
/// The underlying list of constant values.
final List<Constant> value;
/// Creates a [ListConstant] object with the given list of [value]s.
const ListConstant(this.value);
@override
int get hashCode => cacheHashCode(() => deepHash(value));
@override
int get _depth => cacheDepth(() {
var depth = 0;
for (final constant in value) {
depth = max(depth, constant._depth);
}
return 1 + depth;
});
@override
int get _size => cacheSize(() {
var size = 0;
for (final constant in value) {
size += constant._size;
}
return 1 + size;
});
@override
Constant _canonicalizeChildren(CanonicalizationContext context) =>
ListConstant([
for (final c in value) context.canonicalizeConstant(c),
]);
@override
Constant _filter({String? definitionPackageName}) => ListConstant([
for (final c in value)
c._filter(definitionPackageName: definitionPackageName),
]);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ListConstant &&
other._depth == _depth &&
other._size == _size &&
deepEquals(other.value, value);
}
@override
ListConstantSyntax _toSyntax(SerializationContext context) =>
ListConstantSyntax(
value: [for (final constant in value) context.constants[constant]!],
);
@override
int get _orderingTypePriority => 8;
@override
int _compareToSameType(ListConstant other) {
final lengthCompare = value.length.compareTo(other.value.length);
if (lengthCompare != 0) return lengthCompare;
for (var i = 0; i < value.length; i++) {
final itemCompare = value[i]._compareTo(other.value[i]);
if (itemCompare != 0) return itemCompare;
}
return 0;
}
@override
String toString() => 'ListConstant([${value.join(', ')}])';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) {
if (other is! ListConstant) return false;
if (value.length != other.value.length) return false;
for (var i = 0; i < value.length; i++) {
if (!value[i].semanticEquals(
other.value[i],
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
)) {
return false;
}
}
return true;
}
}
/// A constant set of [Constant] values.
final class SetConstant extends Constant {
/// The underlying set of constant values.
final List<Constant> value;
/// Creates a [SetConstant] object with the given set of [value]s.
const SetConstant(this.value);
@override
int get hashCode => cacheHashCode(() => deepHash(value));
@override
int get _depth => cacheDepth(() {
var depth = 0;
for (final constant in value) {
depth = max(depth, constant._depth);
}
return 1 + depth;
});
@override
int get _size => cacheSize(() {
var size = 0;
for (final constant in value) {
size += constant._size;
}
return 1 + size;
});
@override
Constant _canonicalizeChildren(CanonicalizationContext context) {
final canonicalized = [
for (final c in value) context.canonicalizeConstant(c),
];
canonicalized.sort((a, b) => a._compareTo(b));
return SetConstant(canonicalized);
}
@override
Constant _filter({String? definitionPackageName}) {
final filtered = [
for (final c in value)
c._filter(definitionPackageName: definitionPackageName),
];
filtered.sort((a, b) => a._compareTo(b));
return SetConstant(filtered);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is SetConstant &&
other._depth == _depth &&
other._size == _size &&
deepEquals(other.value, value);
}
@override
SetConstantSyntax _toSyntax(SerializationContext context) =>
SetConstantSyntax(
value: [for (final constant in value) context.constants[constant]!],
);
@override
int get _orderingTypePriority => 9;
@override
int _compareToSameType(SetConstant other) {
var compare = value.length.compareTo(other.value.length);
if (compare != 0) return compare;
for (var i = 0; i < value.length; i++) {
compare = value[i]._compareTo(other.value[i]);
if (compare != 0) return compare;
}
return 0;
}
@override
String toString() => 'SetConstant({${value.join(', ')}})';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) {
if (other is! SetConstant) return false;
if (value.length != other.value.length) return false;
// Set equality is tricky for semanticEquals because it's not just identity.
// Check if every element in this set has a semantically equal element in
// the other set.
for (final element in value) {
if (!other.value.any(
(e) => element.semanticEquals(
e,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
),
)) {
return false;
}
}
return true;
}
}
/// A constant map from [Constant] keys to [Constant] values.
final class MapConstant extends Constant {
/// The underlying map of constant values.
final List<MapEntry<Constant, Constant>> entries;
/// Creates a [MapConstant] object with the given map of entries.
const MapConstant(this.entries);
@override
int get hashCode => cacheHashCode(
() => Object.hashAll(
entries.map((e) => Object.hash(e.key, e.value)),
),
);
@override
int get _depth => cacheDepth(() {
var depth = 0;
for (final entry in entries) {
depth = max(depth, max(entry.key._depth, entry.value._depth));
}
return 1 + depth;
});
@override
int get _size => cacheSize(() {
var size = 0;
for (final entry in entries) {
size += entry.key._size + entry.value._size;
}
return 1 + size;
});
@override
Constant _canonicalizeChildren(CanonicalizationContext context) {
final canonEntries = [
for (final e in entries)
MapEntry(
context.canonicalizeConstant(e.key),
context.canonicalizeConstant(e.value),
),
];
canonEntries.sort((a, b) => a.key._compareTo(b.key));
return MapConstant(canonEntries);
}
@override
Constant _filter({String? definitionPackageName}) {
final filtered = [
for (final entry in entries)
MapEntry(
entry.key._filter(definitionPackageName: definitionPackageName),
entry.value._filter(definitionPackageName: definitionPackageName),
),
];
filtered.sort((a, b) => a.key._compareTo(b.key));
return MapConstant(filtered);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! MapConstant) return false;
if (other._depth != _depth || other._size != _size) return false;
if (other.entries.length != entries.length) return false;
for (var i = 0; i < entries.length; i++) {
if (entries[i].key != other.entries[i].key ||
entries[i].value != other.entries[i].value) {
return false;
}
}
return true;
}
@override
MapConstantSyntax _toSyntax(SerializationContext context) =>
MapConstantSyntax(
value: [
for (final entry in entries)
MapEntrySyntax(
key: context.constants[entry.key]!,
value: context.constants[entry.value]!,
),
],
);
@override
int get _orderingTypePriority => 10;
@override
int _compareToSameType(MapConstant other) {
final lengthCompare = entries.length.compareTo(other.entries.length);
if (lengthCompare != 0) return lengthCompare;
for (var i = 0; i < entries.length; i++) {
final keyCompare = entries[i].key._compareTo(other.entries[i].key);
if (keyCompare != 0) return keyCompare;
final valueCompare = entries[i].value._compareTo(other.entries[i].value);
if (valueCompare != 0) return valueCompare;
}
return 0;
}
@override
String toString() =>
'MapConstant({${entries.map((e) => '${e.key}: ${e.value}').join(', ')}})';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) {
if (other is! MapConstant) return false;
if (entries.length != other.entries.length) return false;
for (var i = 0; i < entries.length; i++) {
if (!entries[i].key.semanticEquals(
other.entries[i].key,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
) ||
!entries[i].value.semanticEquals(
other.entries[i].value,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
)) {
return false;
}
}
return true;
}
}
/// A constant instance of a class with its fields.
///
/// Only as far as they can also be represented by constants. This is more or
/// less the same as a [MapConstant].
///
/// Fields initialized by default constructor values are also included in
/// [fields].
final class InstanceConstant extends Constant {
/// The definition of the class of this instance.
final Definition definition;
/// The fields of this instance, mapped from field name to [Constant] value.
final Map<String, Constant> fields;
/// Creates an [InstanceConstant] object with the given [definition] and
/// [fields].
const InstanceConstant({required this.definition, required this.fields});
@override
InstanceConstantSyntax _toSyntax(SerializationContext context) =>
InstanceConstantSyntax(
definitionIndex: context.definitions[definition]!,
value: fields.isNotEmpty
? JsonObjectSyntax.fromJson({
for (final entry in fields.entries)
entry.key: context.constants[entry.value]!,
})
: null,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is InstanceConstant &&
other._depth == _depth &&
other._size == _size &&
other.definition == definition &&
deepEquals(other.fields, fields);
}
@override
int get hashCode =>
cacheHashCode(() => Object.hash(definition, deepHash(fields)));
@override
int get _depth => cacheDepth(() {
var depth = 0;
for (final field in fields.values) {
depth = max(depth, field._depth);
}
return 1 + depth;
});
@override
int get _size => cacheSize(() {
var size = 0;
for (final field in fields.values) {
size += field._size;
}
return 1 + size;
});
@override
Constant _canonicalizeChildren(CanonicalizationContext context) {
final sortedEntries = fields.entries.toList()
..sort((a, b) => a.key.compareTo(b.key));
return InstanceConstant(
definition: context.canonicalizeDefinition(definition),
fields: {
for (final e in sortedEntries)
e.key: context.canonicalizeConstant(e.value),
},
);
}
@override
Constant _filter({String? definitionPackageName}) {
final libraryUri = definition.library.uri;
if (definitionPackageName != null &&
!libraryUri.startsWith('package:$definitionPackageName/')) {
return UnsupportedConstant(
'Instance of $definition from other package is not supported.',
);
}
return InstanceConstant(
definition: definition,
fields: fields.map(
(key, value) => MapEntry(
key,
value._filter(definitionPackageName: definitionPackageName),
),
),
);
}
@override
int get _orderingTypePriority => 13;
@override
int _compareToSameType(InstanceConstant other) {
final definitionCompare = definition.compareTo(other.definition);
if (definitionCompare != 0) return definitionCompare;
final lengthCompare = fields.length.compareTo(other.fields.length);
if (lengthCompare != 0) return lengthCompare;
final sortedKeys = fields.keys.toList()..sort();
final otherSortedKeys = other.fields.keys.toList()..sort();
for (var i = 0; i < sortedKeys.length; i++) {
final keyCompare = sortedKeys[i].compareTo(otherSortedKeys[i]);
if (keyCompare != 0) return keyCompare;
final valueCompare = fields[sortedKeys[i]]!._compareTo(
other.fields[otherSortedKeys[i]]!,
);
if (valueCompare != 0) return valueCompare;
}
return 0;
}
@override
String toString() =>
'InstanceConstant($definition, {'
'${fields.entries.map((e) => '${e.key}: ${e.value}').join(', ')}})';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) {
if (other is! InstanceConstant) return false;
// ignore: invalid_use_of_visible_for_testing_member
if (!definition.semanticEquals(other.definition)) return false;
if (fields.length != other.fields.length) return false;
for (final entry in fields.entries) {
final otherField = other.fields[entry.key];
if (otherField == null ||
!entry.value.semanticEquals(
otherField,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
)) {
return false;
}
}
return true;
}
}
/// A constant enum value.
///
/// Fields initialized by default constructor values (for enhanced enums) are
/// also included in [fields].
final class EnumConstant extends Constant {
/// The definition of the enum class of this value.
final Definition definition;
/// The index of the enum member.
final int index;
/// The name of the enum member.
final String name;
/// The fields of this instance, mapped from field name to [Constant] value.
///
/// This includes additional fields from enhanced enums.
final Map<String, Constant> fields;
/// Creates an [EnumConstant] object with the given [definition], [index],
/// [name], and [fields].
const EnumConstant({
required this.definition,
required this.index,
required this.name,
this.fields = const {},
});
@override
EnumConstantSyntax _toSyntax(SerializationContext context) =>
EnumConstantSyntax(
definitionIndex: context.definitions[definition]!,
index: index,
name: name,
value: fields.isNotEmpty
? {
for (final entry in fields.entries)
entry.key: context.constants[entry.value]!,
}
: null,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is EnumConstant &&
other._depth == _depth &&
other._size == _size &&
other.definition == definition &&
other.index == index &&
other.name == name &&
deepEquals(other.fields, fields);
}
@override
int get hashCode => cacheHashCode(
() => Object.hash(definition, index, name, deepHash(fields)),
);
@override
int get _depth => cacheDepth(() {
var depth = 0;
for (final field in fields.values) {
depth = max(depth, field._depth);
}
return 1 + depth;
});
@override
int get _size => cacheSize(() {
var size = 0;
for (final field in fields.values) {
size += field._size;
}
return 1 + size;
});
@override
Constant _canonicalizeChildren(CanonicalizationContext context) {
final sortedEntries = fields.entries.toList()
..sort((a, b) => a.key.compareTo(b.key));
return EnumConstant(
definition: context.canonicalizeDefinition(definition),
index: index,
name: name,
fields: {
for (final e in sortedEntries)
e.key: context.canonicalizeConstant(e.value),
},
);
}
@override
Constant _filter({String? definitionPackageName}) {
final libraryUri = definition.library.uri;
if (definitionPackageName != null &&
!libraryUri.startsWith('package:$definitionPackageName/')) {
return UnsupportedConstant(
'Instance of $definition from other package is not supported.',
);
}
return EnumConstant(
definition: definition,
index: index,
name: name,
fields: fields.map(
(key, value) => MapEntry(
key,
value._filter(definitionPackageName: definitionPackageName),
),
),
);
}
@override
int get _orderingTypePriority => 12;
@override
int _compareToSameType(EnumConstant other) {
final definitionCompare = definition.compareTo(other.definition);
if (definitionCompare != 0) return definitionCompare;
final indexCompare = index.compareTo(other.index);
if (indexCompare != 0) return indexCompare;
final lengthCompare = fields.length.compareTo(other.fields.length);
if (lengthCompare != 0) return lengthCompare;
final sortedKeys = fields.keys.toList()..sort();
final otherSortedKeys = other.fields.keys.toList()..sort();
for (var i = 0; i < sortedKeys.length; i++) {
final keyCompare = sortedKeys[i].compareTo(otherSortedKeys[i]);
if (keyCompare != 0) return keyCompare;
final valueCompare = fields[sortedKeys[i]]!._compareTo(
other.fields[otherSortedKeys[i]]!,
);
if (valueCompare != 0) return valueCompare;
}
return 0;
}
@override
String toString() =>
'EnumConstant($definition, index: $index, name: $name, fields: {'
'${fields.entries.map((e) => '${e.key}: ${e.value}').join(', ')}})';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) {
if (other is! EnumConstant) return false;
// ignore: invalid_use_of_visible_for_testing_member
if (!definition.semanticEquals(other.definition)) return false;
if (index != other.index || name != other.name) return false;
if (fields.length != other.fields.length) return false;
for (final entry in fields.entries) {
final otherField = other.fields[entry.key];
if (otherField == null ||
!entry.value.semanticEquals(
otherField,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
)) {
return false;
}
}
return true;
}
}
/// A constant record value.
final class RecordConstant extends Constant {
/// The positional fields of this record.
final List<Constant> positional;
/// The named fields of this record.
final Map<String, Constant> named;
/// Creates a [RecordConstant] object with the given [positional] and [named]
/// fields.
const RecordConstant({
this.positional = const [],
this.named = const {},
});
@override
RecordConstantSyntax _toSyntax(SerializationContext context) =>
RecordConstantSyntax(
positional: positional.isNotEmpty
? [for (final c in positional) context.constants[c]!]
: null,
named: named.isNotEmpty
? {
for (final entry in named.entries)
entry.key: context.constants[entry.value]!,
}
: null,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is RecordConstant &&
other._depth == _depth &&
other._size == _size &&
deepEquals(other.positional, positional) &&
deepEquals(other.named, named);
}
@override
int get hashCode => cacheHashCode(
() => Object.hash(deepHash(positional), deepHash(named)),
);
@override
int get _depth => cacheDepth(() {
var depth = 0;
for (final constant in positional) {
depth = max(depth, constant._depth);
}
for (final constant in named.values) {
depth = max(depth, constant._depth);
}
return 1 + depth;
});
@override
int get _size => cacheSize(() {
var size = 0;
for (final constant in positional) {
size += constant._size;
}
for (final constant in named.values) {
size += constant._size;
}
return 1 + size;
});
@override
Constant _canonicalizeChildren(CanonicalizationContext context) {
final sortedNamedEntries = named.entries.toList()
..sort((a, b) => a.key.compareTo(b.key));
return RecordConstant(
positional: [
for (final c in positional) context.canonicalizeConstant(c),
],
named: {
for (final e in sortedNamedEntries)
e.key: context.canonicalizeConstant(e.value),
},
);
}
@override
Constant _filter({String? definitionPackageName}) => RecordConstant(
positional: [
for (final c in positional)
c._filter(definitionPackageName: definitionPackageName),
],
named: named.map(
(key, value) => MapEntry(
key,
value._filter(definitionPackageName: definitionPackageName),
),
),
);
@override
int get _orderingTypePriority => 11;
@override
int _compareToSameType(RecordConstant other) {
var compare = positional.length.compareTo(other.positional.length);
if (compare != 0) return compare;
compare = named.length.compareTo(other.named.length);
if (compare != 0) return compare;
for (var i = 0; i < positional.length; i++) {
compare = positional[i]._compareTo(other.positional[i]);
if (compare != 0) return compare;
}
final sortedKeys = named.keys.toList()..sort();
final otherSortedKeys = other.named.keys.toList()..sort();
for (var i = 0; i < sortedKeys.length; i++) {
compare = sortedKeys[i].compareTo(otherSortedKeys[i]);
if (compare != 0) return compare;
compare = named[sortedKeys[i]]!._compareTo(
other.named[otherSortedKeys[i]]!,
);
if (compare != 0) return compare;
}
return 0;
}
@override
String toString() =>
'RecordConstant('
'${positional.join(', ')}'
'${positional.isNotEmpty && named.isNotEmpty ? ', ' : ''}'
'${named.entries.map((e) => '${e.key}: ${e.value}').join(', ')})';
@override
bool _semanticEqualsInternal(
MaybeConstant other,
bool allowPromotionOfUnsupported,
) {
if (other is! RecordConstant) return false;
if (positional.length != other.positional.length) return false;
if (named.length != other.named.length) return false;
for (var i = 0; i < positional.length; i++) {
if (!positional[i].semanticEquals(
other.positional[i],
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
)) {
return false;
}
}
for (final entry in named.entries) {
final otherField = other.named[entry.key];
if (otherField == null ||
!entry.value.semanticEquals(
otherField,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
)) {
return false;
}
}
return true;
}
}
/// Package private (protected) methods for [MaybeConstant].
///
/// This avoids bloating the public API and public API docs and prevents
/// internal types from leaking from the API.
extension MaybeConstantProtected on MaybeConstant {
/// Converts this constant to a syntax representation.
ConstantSyntax toSyntax(SerializationContext context) => _toSyntax(context);
/// Canonicalizes the children of this constant.
MaybeConstant canonicalizeChildren(CanonicalizationContext context) =>
_canonicalizeChildren(context);
/// Returns a filtered copy of this constant.
MaybeConstant filter({String? definitionPackageName}) =>
_filter(definitionPackageName: definitionPackageName);
/// Compares this constant to [other] for ordering.
int compareTo(MaybeConstant other) => _compareTo(other);
/// Creates a [MaybeConstant] from a [ConstantSyntax].
static MaybeConstant fromSyntax(
ConstantSyntax syntax,
DeserializationContext context,
) => MaybeConstant._fromSyntax(syntax, context);
/// Compares this constant to [other] for semantic equality.
@visibleForTesting
bool semanticEquals(
MaybeConstant other, {
bool allowPromotionOfUnsupported = false,
}) => this.semanticEquals(
other,
allowPromotionOfUnsupported: allowPromotionOfUnsupported,
);
}