blob: 0dabebec81d55114a687d07cd6c0279f8d729be7 [file] [log] [blame] [edit]
// Copyright (c) 2025, 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 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/binary/binary_reader.dart';
import 'package:analyzer/src/binary/binary_writer.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/fine/lookup_name.dart';
import 'package:analyzer/src/fine/manifest_id.dart';
import 'package:analyzer/src/fine/manifest_item.dart';
import 'package:analyzer/src/fine/manifest_type.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
class EncodeContext {
final LinkedElementFactory elementFactory;
final Map<TypeParameterElement, int> _typeParameters = Map.identity();
final Map<FormalParameterElement, int> _formalParameters = Map.identity();
EncodeContext({required this.elementFactory});
/// Returns the id of [element], or `null` if from this bundle.
ManifestItemId? getElementId(Element element) {
return elementFactory.getElementId(element);
}
int indexOfFormalParameter(FormalParameterElement element) {
if (_formalParameters[element] case var result?) {
return result;
}
throw StateError('No formal parameter $element');
}
int indexOfTypeParameter(TypeParameterElement element) {
if (_typeParameters[element] case var bottomIndex?) {
return _typeParameters.length - 1 - bottomIndex;
}
return throw StateError('No type parameter $element');
}
T withFormalParameters<T>(
List<FormalParameterElement> formalParameters,
T Function() operation,
) {
for (var formalParameter in formalParameters) {
_formalParameters[formalParameter] = _formalParameters.length;
}
try {
return operation();
} finally {
for (var formalParameter in formalParameters) {
_formalParameters.remove(formalParameter);
}
}
}
T withTypeParameters<T>(
List<TypeParameterElementImpl> typeParameters,
T Function(List<ManifestTypeParameter> typeParameters) operation,
) {
for (var typeParameter in typeParameters) {
_typeParameters[typeParameter] = _typeParameters.length;
}
var encoded = <ManifestTypeParameter>[
for (var typeParameter in typeParameters)
ManifestTypeParameter.encode(this, typeParameter),
];
try {
return operation(encoded);
} finally {
for (var typeParameter in typeParameters) {
_typeParameters.remove(typeParameter);
}
}
}
}
/// The description of an element referenced by a result library.
///
/// For example, if we encode `int get foo`, we want to know that the return
/// type of this getter references `int` from `dart:core`. How exactly we
/// arrived to this type is not important (for the manifest, but not for
/// requirements); it could be `final int foo = 0;` or `final foo = 0;`.
///
/// So, when we link the library next time, and compare the result with the
/// previous manifest, we can check if all the referenced elements are the
/// same.
final class ManifestElement {
/// The URI of the library that declares the element.
final Uri libraryUri;
/// The kind, mostly to distinguish fields and getters.
final ManifestElementKind kind;
/// The top-level element name.
final String topLevelName;
/// The member name, e.g. a method name.
final String? memberName;
/// The id of the element, if not from the same bundle.
final ManifestItemId? id;
ManifestElement({
required this.libraryUri,
required this.kind,
required this.topLevelName,
required this.memberName,
required this.id,
});
factory ManifestElement.read(BinaryReader reader) {
return ManifestElement(
libraryUri: reader.readUri(),
kind: reader.readEnum(ManifestElementKind.values),
topLevelName: reader.readStringReference(),
memberName: reader.readOptionalStringReference(),
id: reader.readOptionalObject(() => ManifestItemId.read(reader)),
);
}
@override
int get hashCode => Object.hash(libraryUri, kind, topLevelName, memberName);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ManifestElement &&
other.libraryUri == libraryUri &&
other.kind == kind &&
other.topLevelName == topLevelName &&
other.memberName == memberName;
}
/// If [element] matches this description, records the reference and id.
/// If not, returns `false`, it is a mismatch anyway.
bool match(MatchContext context, Element element) {
var enclosingElement = element.enclosingElement!;
Element givenTopLevelElement;
Element? givenMemberElement;
if (enclosingElement is LibraryElement) {
givenTopLevelElement = element;
} else {
givenTopLevelElement = enclosingElement;
givenMemberElement = element;
}
var givenLibraryUri = enclosingElement.library!.uri;
if (givenLibraryUri != libraryUri) {
return false;
}
if (ManifestElementKind.of(element) != kind) {
return false;
}
if (givenTopLevelElement.lookupName != topLevelName) {
return false;
}
if (givenMemberElement?.lookupName != memberName) {
return false;
}
context.elements.add(element);
if (id case var id?) {
context.externalIds[element] = id;
}
return true;
}
void write(BinaryWriter writer) {
writer.writeUri(libraryUri);
writer.writeEnum(kind);
writer.writeStringReference(topLevelName);
writer.writeOptionalStringReference(memberName);
id.writeOptional(writer);
}
static ManifestElement encode(EncodeContext context, Element element) {
Element topLevelElement;
Element? memberElement;
var enclosingElement = element.enclosingElement!;
if (enclosingElement is LibraryElement) {
topLevelElement = element;
} else {
topLevelElement = enclosingElement;
assert(topLevelElement.enclosingElement is LibraryElement);
memberElement = element;
}
return ManifestElement(
libraryUri: topLevelElement.library!.uri,
kind: ManifestElementKind.of(element),
topLevelName: topLevelElement.lookupName!,
memberName: memberElement?.lookupName,
id: context.getElementId(element),
);
}
static ManifestElement? encodeOptional(
EncodeContext context,
Element? element,
) {
return element != null ? encode(context, element) : null;
}
static List<ManifestElement> readList(BinaryReader reader) {
return reader.readTypedList(() => ManifestElement.read(reader));
}
static ManifestElement? readOptional(BinaryReader reader) {
return reader.readOptionalObject(() => ManifestElement.read(reader));
}
}
/// Note, "instance" means inside [InstanceElement], not as "not static".
enum ManifestElementKind {
class_,
enum_,
extension_,
extensionType,
mixin_,
typeAlias,
topLevelVariable,
topLevelGetter,
topLevelSetter,
topLevelFunction,
instanceField,
instanceGetter,
instanceSetter,
instanceMethod,
interfaceConstructor;
static ManifestElementKind of(Element element) {
switch (element) {
case ClassElement():
return ManifestElementKind.class_;
case EnumElement():
return ManifestElementKind.enum_;
case ExtensionElement():
return ManifestElementKind.extension_;
case ExtensionTypeElement():
return ManifestElementKind.extensionType;
case MixinElement():
return ManifestElementKind.mixin_;
case TopLevelVariableElement():
return ManifestElementKind.topLevelVariable;
case GetterElement():
if (element.enclosingElement is LibraryElement) {
return ManifestElementKind.topLevelGetter;
}
return ManifestElementKind.instanceGetter;
case SetterElement():
if (element.enclosingElement is LibraryElement) {
return ManifestElementKind.topLevelSetter;
}
return ManifestElementKind.instanceSetter;
case TopLevelFunctionElement():
return ManifestElementKind.topLevelFunction;
case FieldElement():
return ManifestElementKind.instanceField;
case MethodElement():
return ManifestElementKind.instanceMethod;
case ConstructorElement():
return ManifestElementKind.interfaceConstructor;
case TypeAliasElement():
return ManifestElementKind.typeAlias;
default:
throw StateError('Unexpected (${element.runtimeType}) $element');
}
}
}
class MatchContext {
final MatchContext? parent;
/// Any referenced elements, from this bundle or not.
final Set<Element> elements = {};
/// The required identifiers of referenced elements that are not from this
/// bundle.
final Map<Element, ManifestItemId> externalIds = {};
final Map<TypeParameterElement, int> _typeParameters = Map.identity();
final Map<FormalParameterElement, int> _formalParameters = Map.identity();
MatchContext({required this.parent});
/// Any referenced elements, from this bundle or not.
List<Element> get elementList => elements.toList(growable: false);
void addTypeParameters(List<TypeParameterElement> typeParameters) {
for (var typeParameter in typeParameters) {
_typeParameters[typeParameter] = _typeParameters.length;
}
}
int indexOfFormalParameter(FormalParameterElement element) {
if (_formalParameters[element] case var result?) {
return result;
}
throw StateError('No formal parameter $element');
}
int indexOfTypeParameter(TypeParameterElement element) {
if (_typeParameters[element] case var result?) {
return _typeParameters.length - 1 - result;
}
if (parent case var parent?) {
var parentIndex = parent.indexOfTypeParameter(element);
return _typeParameters.length + parentIndex;
}
throw StateError('No type parameter $element');
}
T withFormalParameters<T>(
List<FormalParameterElement> formalParameters,
T Function() operation,
) {
for (var formalParameter in formalParameters) {
_formalParameters[formalParameter] = _formalParameters.length;
}
try {
return operation();
} finally {
for (var formalParameter in formalParameters) {
_formalParameters.remove(formalParameter);
}
}
}
T withTypeParameters<T>(
List<TypeParameterElement> typeParameters,
T Function() operation,
) {
addTypeParameters(typeParameters);
try {
return operation();
} finally {
for (var typeParameter in typeParameters) {
_typeParameters.remove(typeParameter);
}
}
}
}
extension LinkedElementFactoryExtension on LinkedElementFactory {
/// Returns the id of [element], or `null` if from this bundle.
ManifestItemId? getElementId(Element element) {
Element topLevelElement;
Element? memberElement;
if (element.enclosingElement is LibraryElement) {
topLevelElement = element;
} else {
topLevelElement = element.enclosingElement!;
memberElement = element;
}
// SAFETY: if we can reference the element, it is in a library.
var libraryUri = topLevelElement.library!.uri;
// Prepare the external library manifest.
var manifest = libraryManifests[libraryUri]?.instance;
if (manifest == null) {
return null;
}
// SAFETY: if we can reference the element, it has a name.
var topLevelName = topLevelElement.lookupName!.asLookupName;
if (manifest.declaredConflicts[topLevelName] case var id?) {
return id;
}
ManifestItem? topLevelItem;
switch (topLevelElement) {
case ClassElement():
topLevelItem = manifest.declaredClasses[topLevelName];
case EnumElement():
topLevelItem = manifest.declaredEnums[topLevelName];
case ExtensionElement():
topLevelItem = manifest.declaredExtensions[topLevelName];
case ExtensionTypeElement():
topLevelItem = manifest.declaredExtensionTypes[topLevelName];
case MixinElement():
topLevelItem = manifest.declaredMixins[topLevelName];
case GetterElement():
return manifest.declaredGetters[topLevelName]!.id;
case SetterElement():
return manifest.declaredSetters[topLevelName]!.id;
case TopLevelFunctionElement():
return manifest.declaredFunctions[topLevelName]!.id;
case TopLevelVariableElement():
return manifest.declaredVariables[topLevelName]!.id;
case TypeAliasElement():
return manifest.declaredTypeAliases[topLevelName]!.id;
}
if (topLevelItem == null) {
throw StateError(
'Missing element manifest: (${topLevelElement.runtimeType}) '
'$topLevelElement in $libraryUri',
);
}
if (memberElement == null) {
return topLevelItem.id;
}
// If not top-level element, then a member in [InstanceElement].
var memberName = memberElement.lookupName!.asLookupName;
topLevelItem as InstanceItem;
switch (element) {
case FieldElement():
if (topLevelItem.getDeclaredFieldId(memberName) case var result?) {
return result;
}
case GetterElement():
if (topLevelItem.getDeclaredGetterId(memberName) case var result?) {
return result;
}
case SetterElement():
if (topLevelItem.getDeclaredSetterId(memberName) case var result?) {
return result;
}
case MethodElement():
if (topLevelItem.getDeclaredMethodId(memberName) case var result?) {
return result;
}
}
// If we get here, the top-level container is not [ExtensionElement].
// So, it must be [InterfaceElement].
topLevelItem as InterfaceItem;
if (element is ConstructorElement) {
return topLevelItem.getConstructorId(memberName)!;
}
// In rare cases the member is not declared by the element, but added
// to the interface as a result of top-merge.
return topLevelItem.getInterfaceMethodId(memberName) ??
(throw '[runtimeType: ${element.runtimeType}]'
'[topLevelName: $topLevelName]'
'[memberName: $memberName]');
}
}
extension ManifestElementExtension on ManifestElement? {
bool match(MatchContext context, Element? element) {
if (this case var self?) {
return element != null && self.match(context, element);
}
return element == null;
}
void writeOptional(BinaryWriter writer) {
writer.writeOptionalObject(this, (it) {
it.write(writer);
});
}
}