// 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 _typeParameters = Map.identity(); final Map _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( List 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( List typeParameters, T Function(List typeParameters) operation, ) { for (var typeParameter in typeParameters) { _typeParameters[typeParameter] = _typeParameters.length; } var encoded = [ 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 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 elements = {}; /// The required identifiers of referenced elements that are not from this /// bundle. final Map externalIds = {}; final Map _typeParameters = Map.identity(); final Map _formalParameters = Map.identity(); MatchContext({required this.parent}); /// Any referenced elements, from this bundle or not. List get elementList => elements.toList(growable: false); void addTypeParameters(List 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( List 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( List 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); }); } }