| // 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 '../builder/builder.dart'; |
| import '../builder/declaration_builders.dart'; |
| import '../builder/library_builder.dart'; |
| import 'lookup_result.dart'; |
| import 'scope.dart'; |
| import 'uris.dart'; |
| |
| abstract class NameSpace { |
| /// Returns the [LookupResult] for the [Builder]s of the given [name] in the |
| /// name space. |
| LookupResult? lookup(String name); |
| |
| void forEachLocalExtension(void Function(ExtensionBuilder member) f); |
| } |
| |
| abstract class MutableNameSpace implements NameSpace { |
| void addLocalMember(String name, NamedBuilder member, {required bool setter}); |
| } |
| |
| abstract class ComputedNameSpace implements NameSpace { |
| /// Returns a filtered iterator of members and setters mapped in this name |
| /// space. |
| /// |
| /// Only members of type [T] are included. If [parent] is provided, on members |
| /// declared in [parent] are included. Duplicates are not included. |
| Iterator<T> filteredIterator<T extends NamedBuilder>(); |
| } |
| |
| abstract class ComputedMutableNameSpace |
| implements MutableNameSpace, ComputedNameSpace { |
| factory ComputedMutableNameSpace() = ComputedMutableNameSpaceImpl._; |
| |
| /// Adds [builder] to the extensions in this name space. |
| void addExtension(ExtensionBuilder builder); |
| |
| void replaceLocalMember( |
| String name, |
| NamedBuilder member, { |
| required bool setter, |
| }); |
| } |
| |
| abstract class DeclarationNameSpace implements NameSpace { |
| final Map<String, MemberLookupResult> _content; |
| final Map<String, MemberLookupResult> _constructors; |
| |
| DeclarationNameSpace({ |
| required Map<String, MemberLookupResult> content, |
| required Map<String, MemberLookupResult> constructors, |
| }) : _content = content, |
| _constructors = constructors; |
| |
| @override |
| void forEachLocalExtension(void Function(ExtensionBuilder member) f) {} |
| |
| @override |
| MemberLookupResult? lookup(String name) => _content[name]; |
| |
| MemberLookupResult? lookupConstructor(String name) => _constructors[name]; |
| } |
| |
| base class ComputedMutableNameSpaceImpl implements ComputedMutableNameSpace { |
| Map<String, LookupResult>? _content; |
| Set<ExtensionBuilder>? _extensions; |
| |
| ComputedMutableNameSpaceImpl._(); |
| |
| @override |
| void addLocalMember( |
| String name, |
| NamedBuilder member, { |
| required bool setter, |
| }) { |
| Map<String, LookupResult> content = _content ??= {}; |
| LookupResult? existing = content[name]; |
| if (existing != null) { |
| if (setter) { |
| assert( |
| existing.setable == null || |
| // Coverage-ignore(suite): Not run. |
| existing.setable == member, |
| "Trying to map setable $member to $name " |
| "replacing the existing value ${existing.setable}", |
| ); |
| if (existing.getable != null) { |
| content[name] = new GetableSetableResult(existing.getable!, member); |
| return; |
| } |
| } else { |
| assert( |
| existing.getable == null || existing.getable == member, |
| "Trying to map getable $member to $name " |
| "replacing the existing value ${existing.getable}", |
| ); |
| if (existing.setable != null) { |
| content[name] = new GetableSetableResult(member, existing.setable!); |
| return; |
| } |
| } |
| } |
| if (member is LookupResult) { |
| content[name] = member as LookupResult; |
| } else { |
| content[name] = setter |
| ? |
| // Coverage-ignore(suite): Not run. |
| new SetableResult(member) |
| : new GetableResult(member); |
| } |
| } |
| |
| @override |
| void replaceLocalMember( |
| String name, |
| NamedBuilder member, { |
| required bool setter, |
| }) { |
| Map<String, LookupResult> content = _content!; |
| LookupResult? existing = content[name]; |
| assert(existing != null, "No existing result for $name."); |
| if (existing != null) { |
| if (setter) { |
| assert( |
| existing.setable != null, |
| "Trying to map setable $member to $name " |
| "replacing the existing value ${existing.setable}", |
| ); |
| if (existing.getable != null) { |
| // Coverage-ignore-block(suite): Not run. |
| content[name] = new GetableSetableResult(existing.getable!, member); |
| return; |
| } |
| } else { |
| assert( |
| existing.getable != null, |
| "Trying to map setable $member to $name " |
| "replacing the existing value ${existing.getable}", |
| ); |
| if (existing.setable != null) { |
| content[name] = new GetableSetableResult(member, existing.setable!); |
| return; |
| } |
| } |
| } |
| if (member is LookupResult) { |
| content[name] = member as LookupResult; |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| content[name] = setter |
| ? new SetableResult(member) |
| : new GetableResult(member); |
| } |
| } |
| |
| @override |
| void addExtension(ExtensionBuilder builder) { |
| (_extensions ??= {}).add(builder); |
| } |
| |
| @override |
| Iterator<T> filteredIterator<T extends NamedBuilder>() { |
| return new FilteredIterator<T>( |
| new LookupResultIterator( |
| _content?.values.iterator, |
| _extensions |
| // Coverage-ignore(suite): Not run. |
| ?.iterator, |
| ), |
| includeDuplicates: false, |
| ); |
| } |
| |
| @override |
| void forEachLocalExtension(void Function(ExtensionBuilder member) f) { |
| _extensions?.forEach(f); |
| } |
| |
| @override |
| LookupResult? lookup(String name) => _content?[name]; |
| } |
| |
| final class LibraryNameSpace implements NameSpace { |
| final Map<String, LookupResult> _content; |
| final Set<ExtensionBuilder>? _extensions; |
| |
| LibraryNameSpace({ |
| required Map<String, LookupResult> content, |
| required Set<ExtensionBuilder> extensions, |
| }) : _content = content, |
| _extensions = extensions; |
| |
| void addLocalMember(String name, LookupResult member) { |
| assert( |
| !_content.containsKey(name), |
| "Unexpected replacement of ${_content[name]} by $member.", |
| ); |
| _content[name] = member; |
| } |
| |
| @override |
| void forEachLocalExtension(void Function(ExtensionBuilder member) f) { |
| _extensions?.forEach(f); |
| } |
| |
| @override |
| LookupResult? lookup(String name) => _content[name]; |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| /// Returns a string with an error message if [sourceNameSpace] and |
| /// [dillNameSpace] are not equivalent. |
| /// |
| /// This should be used for assertions only. |
| String? areNameSpacesEquivalent({ |
| required Uri importUri, |
| required NameSpace sourceNameSpace, |
| required NameSpace dillNameSpace, |
| }) { |
| sourceNameSpace as ComputedMutableNameSpaceImpl; |
| dillNameSpace as ComputedMutableNameSpaceImpl; |
| |
| bool isEquivalent = true; |
| StringBuffer sb = new StringBuffer(); |
| sb.writeln('Mismatch on ${importUri}:'); |
| |
| Map<String, LookupResult>? sourceContent = sourceNameSpace._content; |
| Map<String, LookupResult>? dillContent = dillNameSpace._content; |
| |
| if (sourceContent != null) { |
| for (MapEntry<String, LookupResult> sourceEntry in sourceContent.entries) { |
| LookupResult sourceResult = sourceEntry.value; |
| LookupResult? dillResult = dillContent?[sourceEntry.key]; |
| if (sourceResult.getable != null) { |
| if (dillResult?.getable == null) { |
| if ((sourceEntry.key == 'dynamic' || sourceEntry.key == 'Never') && |
| importUri == dartCore) { |
| // The source library builder for dart:core has synthetically |
| // injected builders for `dynamic` and `Never` which do not have |
| // corresponding classes in the AST. |
| } else { |
| sb.writeln( |
| 'No dill getable for ${sourceEntry.key}: ${sourceResult}', |
| ); |
| isEquivalent = false; |
| } |
| } |
| } |
| if (sourceResult.setable != null) { |
| if (dillResult?.setable == null) { |
| sb.writeln('No dill setable for ${sourceEntry.key}: ${sourceResult}'); |
| isEquivalent = false; |
| } |
| } |
| } |
| } |
| if (dillContent != null) { |
| for (MapEntry<String, LookupResult> dillEntry in dillContent.entries) { |
| LookupResult dillResult = dillEntry.value; |
| LookupResult? sourceResult = sourceContent?[dillEntry.key]; |
| if (dillResult.getable != null) { |
| if (sourceResult?.getable != null) { |
| sb.writeln('No source getable for ${dillEntry.key}=: ${dillResult}'); |
| isEquivalent = false; |
| } |
| } |
| if (dillResult.setable != null) { |
| if (sourceResult?.setable != null) { |
| sb.writeln('No source setable for ${dillEntry.key}=: ${dillResult}'); |
| isEquivalent = false; |
| } |
| } |
| } |
| } |
| |
| if (isEquivalent) { |
| return null; |
| } |
| return sb.toString(); |
| } |
| |
| final class SourceDeclarationNameSpace extends DeclarationNameSpace { |
| SourceDeclarationNameSpace({ |
| required super.content, |
| required super.constructors, |
| }); |
| |
| void addConstructor(String name, MemberLookupResult constructor) { |
| assert( |
| !_constructors.containsKey(name), |
| "Unexpected existing constructor ${_constructors[name]}, " |
| "trying to add ${constructor}.", |
| ); |
| _constructors[name] = constructor; |
| } |
| |
| void addLocalMember(String name, MemberLookupResult member) { |
| assert( |
| !_content.containsKey(name), |
| "Unexpected replacement of ${_content[name]} by $member.", |
| ); |
| _content[name] = member; |
| } |
| } |
| |
| final class DillDeclarationNameSpace extends DeclarationNameSpace { |
| DillDeclarationNameSpace({ |
| required super.content, |
| required super.constructors, |
| }); |
| } |
| |
| final class DillExportNameSpace extends ComputedMutableNameSpaceImpl { |
| DillExportNameSpace() : super._(); |
| |
| /// Patch up the scope, using the two replacement maps to replace builders in |
| /// scope. The replacement maps from old LibraryBuilder to map, mapping |
| /// from name to new (replacement) builder. |
| void patchUpScope(Map<LibraryBuilder, NameSpace> replacementNameSpaceMap) { |
| // In the following we refer to non-setters as 'getters' for brevity. |
| // |
| // We have to replace all getters and setters in [_locals] and [_setters] |
| // with the corresponding getters and setters in [replacementMap] |
| // and [replacementMapSetters]. |
| // |
| // Since field builders can be replaced by getter and setter builders and |
| // vice versa when going from source to dill builder and back, we might not |
| // have a 1-to-1 relationship between the existing and replacing builders. |
| // |
| // For this reason we start by collecting the names of all getters/setters |
| // that need (some) replacement. Afterwards we go through these names |
| // handling both getters and setters at the same time. |
| |
| Set<String> replacedNames = {}; |
| _content?.forEach((String name, LookupResult result) { |
| if (replacementNameSpaceMap.containsKey(result.getable?.parent)) { |
| replacedNames.add(name); |
| } |
| if (replacementNameSpaceMap.containsKey(result.setable?.parent)) { |
| replacedNames.add(name); |
| } |
| }); |
| if (replacedNames.isNotEmpty) { |
| for (String name in replacedNames) { |
| // We start be collecting the relation between an existing getter/setter |
| // and the getter/setter that will replace it. This information is used |
| // below to handle all the different cases that can occur. |
| LookupResult existingResult = _content![name]!; |
| NamedBuilder? existingGetter = existingResult.getable; |
| NamedBuilder? existingSetter = existingResult.setable; |
| LookupResult? replacementResult; |
| if (existingGetter != null && existingSetter != null) { |
| if (existingGetter == existingSetter) { |
| replacementResult = replacementNameSpaceMap[existingGetter.parent]! |
| .lookup(name); |
| } else { |
| NamedBuilder? replacementGetter = |
| replacementNameSpaceMap[existingGetter.parent] |
| ?.lookup(name) |
| ?.getable; |
| NamedBuilder? replacementSetter = |
| replacementNameSpaceMap[existingSetter.parent] |
| ?.lookup(name) |
| ?.setable; |
| replacementResult = LookupResult.createResult( |
| replacementGetter ?? existingGetter, |
| replacementSetter ?? existingSetter, |
| ); |
| } |
| } else if (existingGetter != null) { |
| replacementResult = LookupResult.createResult( |
| replacementNameSpaceMap[existingGetter.parent] |
| ?.lookup(name) |
| ?.getable, |
| null, |
| ); |
| } else if (existingSetter != null) { |
| replacementResult = LookupResult.createResult( |
| null, |
| replacementNameSpaceMap[existingSetter.parent] |
| ?.lookup(name) |
| ?.setable, |
| ); |
| } |
| if (replacementResult != null) { |
| (_content ??= // Coverage-ignore(suite): Not run. |
| {})[name] = |
| replacementResult; |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| _content?.remove(name); |
| } |
| } |
| } |
| |
| if (_extensions != null) { |
| // Coverage-ignore-block(suite): Not run. |
| bool needsPatching = false; |
| for (ExtensionBuilder extensionBuilder in _extensions!) { |
| if (replacementNameSpaceMap.containsKey( |
| extensionBuilder.libraryBuilder, |
| )) { |
| needsPatching = true; |
| break; |
| } |
| } |
| if (needsPatching) { |
| Set<ExtensionBuilder> extensionsReplacement = |
| new Set<ExtensionBuilder>(); |
| for (ExtensionBuilder extensionBuilder in _extensions!) { |
| if (replacementNameSpaceMap.containsKey( |
| extensionBuilder.libraryBuilder, |
| )) { |
| assert( |
| replacementNameSpaceMap[extensionBuilder.libraryBuilder]! |
| .lookup(extensionBuilder.name)! |
| .getable != |
| null, |
| ); |
| extensionsReplacement.add( |
| replacementNameSpaceMap[extensionBuilder.libraryBuilder]! |
| .lookup(extensionBuilder.name)! |
| .getable |
| as ExtensionBuilder, |
| ); |
| break; |
| } else { |
| extensionsReplacement.add(extensionBuilder); |
| } |
| } |
| _extensions!.clear(); |
| extensionsReplacement.addAll(extensionsReplacement); |
| } |
| } |
| } |
| } |