| // 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); |
| }); |
| } |
| } |