| // Copyright (c) 2016, 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. |
| |
| library fasta.scope; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart'; |
| |
| import 'builder/builder.dart'; |
| import 'builder/extension_builder.dart'; |
| import 'builder/library_builder.dart'; |
| import 'builder/member_builder.dart'; |
| import 'builder/name_iterator.dart'; |
| import 'builder/type_variable_builder.dart'; |
| import 'fasta_codes.dart' |
| show |
| LocatedMessage, |
| Message, |
| messageInternalProblemExtendingUnmodifiableScope, |
| templateAccessError, |
| templateDuplicatedDeclarationUse, |
| templateDuplicatedNamePreviouslyUsedCause; |
| import 'kernel/body_builder.dart' show JumpTarget; |
| import 'kernel/hierarchy/class_member.dart' show ClassMember; |
| import 'kernel/kernel_helper.dart'; |
| import 'problems.dart' show internalProblem, unsupported; |
| import 'source/source_library_builder.dart'; |
| import 'source/source_member_builder.dart'; |
| import 'util/helpers.dart' show DelayedActionPerformer; |
| |
| class MutableScope { |
| /// Names declared in this scope. |
| Map<String, Builder> _local; |
| |
| /// Setters declared in this scope. |
| Map<String, MemberBuilder> _setters; |
| |
| /// The extensions declared in this scope. |
| /// |
| /// This includes all available extensions even if the extensions are not |
| /// accessible by name because of duplicate imports. |
| /// |
| /// For instance: |
| /// |
| /// lib1.dart: |
| /// extension Extension on String { |
| /// method1() {} |
| /// staticMethod1() {} |
| /// } |
| /// lib2.dart: |
| /// extension Extension on String { |
| /// method2() {} |
| /// staticMethod2() {} |
| /// } |
| /// main.dart: |
| /// import 'lib1.dart'; |
| /// import 'lib2.dart'; |
| /// |
| /// main() { |
| /// 'foo'.method1(); // This method is available. |
| /// 'foo'.method2(); // This method is available. |
| /// // These methods are not available because Extension is ambiguous: |
| /// Extension.staticMethod1(); |
| /// Extension.staticMethod2(); |
| /// } |
| /// |
| Set<ExtensionBuilder>? _extensions; |
| |
| /// The scope that this scope is nested within, or `null` if this is the top |
| /// level scope. |
| Scope? _parent; |
| |
| final String classNameOrDebugName; |
| |
| MutableScope(this._local, this._setters, this._extensions, this._parent, |
| this.classNameOrDebugName) { |
| // ignore: unnecessary_null_comparison |
| assert(classNameOrDebugName != null); |
| } |
| |
| Scope? get parent => _parent; |
| |
| @override |
| String toString() => "Scope($classNameOrDebugName, ${_local.keys})"; |
| } |
| |
| class Scope extends MutableScope { |
| /// Indicates whether an attempt to declare new names in this scope should |
| /// succeed. |
| final bool isModifiable; |
| |
| Map<String, JumpTarget>? labels; |
| |
| Map<String, JumpTarget>? forwardDeclaredLabels; |
| |
| Map<String, int>? usedNames; |
| |
| Scope( |
| {required Map<String, Builder> local, |
| Map<String, MemberBuilder>? setters, |
| Set<ExtensionBuilder>? extensions, |
| Scope? parent, |
| required String debugName, |
| this.isModifiable: true}) |
| : super(local, setters = setters ?? const <String, MemberBuilder>{}, |
| extensions, parent, debugName); |
| |
| Scope.top({bool isModifiable: false}) |
| : this( |
| local: <String, Builder>{}, |
| setters: <String, MemberBuilder>{}, |
| debugName: "top", |
| isModifiable: isModifiable); |
| |
| Scope.immutable() |
| : this( |
| local: const <String, Builder>{}, |
| setters: const <String, MemberBuilder>{}, |
| debugName: "immutable", |
| isModifiable: false); |
| |
| Scope.nested(Scope parent, String debugName, {bool isModifiable: true}) |
| : this( |
| local: <String, Builder>{}, |
| setters: <String, MemberBuilder>{}, |
| parent: parent, |
| debugName: debugName, |
| isModifiable: isModifiable); |
| |
| Iterator<Builder> get iterator { |
| return new ScopeLocalDeclarationIterator(this); |
| } |
| |
| NameIterator get nameIterator { |
| return new ScopeLocalDeclarationNameIterator(this); |
| } |
| |
| void debug() { |
| print("Locals:"); |
| _local.forEach((key, value) { |
| print(" $key: $value (${identityHashCode(value)}) (${value.parent})"); |
| }); |
| print("Setters:"); |
| _setters.forEach((key, value) { |
| print(" $key: $value (${identityHashCode(value)}) (${value.parent})"); |
| }); |
| print("Extensions:"); |
| _extensions?.forEach((v) { |
| print(" $v"); |
| }); |
| } |
| |
| /// Patch up the scope, using the two replacement maps to replace builders in |
| /// scope. The replacement maps maps from old LibraryBuilder to map, mapping |
| /// from name to new (replacement) builder. |
| void patchUpScope(Map<LibraryBuilder, Map<String, Builder>> replacementMap, |
| Map<LibraryBuilder, Map<String, Builder>> replacementMapSetters) { |
| // 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 = {}; |
| _local.forEach((String name, Builder builder) { |
| if (replacementMap.containsKey(builder.parent)) { |
| replacedNames.add(name); |
| } |
| }); |
| _setters.forEach((String name, Builder builder) { |
| if (replacementMapSetters.containsKey(builder.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. |
| Builder? existingGetter = _local[name]; |
| LibraryBuilder? replacementLibraryBuilderFromGetter; |
| Builder? replacementGetterFromGetter; |
| Builder? replacementSetterFromGetter; |
| if (existingGetter != null && |
| replacementMap.containsKey(existingGetter.parent)) { |
| replacementLibraryBuilderFromGetter = |
| existingGetter.parent as LibraryBuilder; |
| replacementGetterFromGetter = |
| replacementMap[replacementLibraryBuilderFromGetter]![name]; |
| replacementSetterFromGetter = |
| replacementMapSetters[replacementLibraryBuilderFromGetter]![name]; |
| } |
| Builder? existingSetter = _setters[name]; |
| LibraryBuilder? replacementLibraryBuilderFromSetter; |
| Builder? replacementGetterFromSetter; |
| Builder? replacementSetterFromSetter; |
| if (existingSetter != null && |
| replacementMap.containsKey(existingSetter.parent)) { |
| replacementLibraryBuilderFromSetter = |
| existingSetter.parent as LibraryBuilder; |
| replacementGetterFromSetter = |
| replacementMap[replacementLibraryBuilderFromSetter]![name]; |
| replacementSetterFromSetter = |
| replacementMapSetters[replacementLibraryBuilderFromSetter]![name]; |
| } |
| |
| if (existingGetter == null) { |
| // No existing getter. |
| if (replacementGetterFromSetter != null) { |
| // We might have had one implicitly from the setter. Use it here, |
| // if so. (This is currently not possible, but added to match the |
| // case for setters below.) |
| _local[name] = replacementGetterFromSetter; |
| } |
| } else if (existingGetter.parent == |
| replacementLibraryBuilderFromGetter) { |
| // The existing getter should be replaced. |
| if (replacementGetterFromGetter != null) { |
| // With a new getter. |
| _local[name] = replacementGetterFromGetter; |
| } else { |
| // With `null`, i.e. removed. This means that the getter is |
| // implicitly available through the setter. (This is currently not |
| // possible, but handled here to match the case for setters below). |
| _local.remove(name); |
| } |
| } else { |
| // Leave the getter in - it wasn't replaced. |
| } |
| if (existingSetter == null) { |
| // No existing setter. |
| if (replacementSetterFromGetter != null) { |
| // We might have had one implicitly from the getter. Use it here, |
| // if so. |
| _setters[name] = replacementSetterFromGetter as MemberBuilder; |
| } |
| } else if (existingSetter.parent == |
| replacementLibraryBuilderFromSetter) { |
| // The existing setter should be replaced. |
| if (replacementSetterFromSetter != null) { |
| // With a new setter. |
| _setters[name] = replacementSetterFromSetter as MemberBuilder; |
| } else { |
| // With `null`, i.e. removed. This means that the setter is |
| // implicitly available through the getter. This happens when the |
| // getter is a field builder for an assignable field. |
| _setters.remove(name); |
| } |
| } else { |
| // Leave the setter in - it wasn't replaced. |
| } |
| } |
| } |
| if (_extensions != null) { |
| bool needsPatching = false; |
| for (ExtensionBuilder extensionBuilder in _extensions!) { |
| if (replacementMap.containsKey(extensionBuilder.parent)) { |
| needsPatching = true; |
| break; |
| } |
| } |
| if (needsPatching) { |
| Set<ExtensionBuilder> extensionsReplacement = |
| new Set<ExtensionBuilder>(); |
| for (ExtensionBuilder extensionBuilder in _extensions!) { |
| if (replacementMap.containsKey(extensionBuilder.parent)) { |
| assert(replacementMap[extensionBuilder.parent]![ |
| extensionBuilder.name] != |
| null); |
| extensionsReplacement.add( |
| replacementMap[extensionBuilder.parent]![extensionBuilder.name] |
| as ExtensionBuilder); |
| break; |
| } else { |
| extensionsReplacement.add(extensionBuilder); |
| } |
| } |
| _extensions!.clear(); |
| extensionsReplacement.addAll(extensionsReplacement); |
| } |
| } |
| } |
| |
| Scope copyWithParent(Scope parent, String debugName) { |
| return new Scope( |
| local: super._local, |
| setters: super._setters, |
| extensions: _extensions, |
| parent: parent, |
| debugName: debugName, |
| isModifiable: isModifiable); |
| } |
| |
| /// Don't use this. Use [becomePartOf] instead. |
| void set parent(_) => unsupported("parent=", -1, null); |
| |
| /// This scope becomes equivalent to [scope]. This is used for parts to |
| /// become part of their library's scope. |
| void becomePartOf(Scope scope) { |
| assert(_parent!._parent == null); |
| assert(scope._parent!._parent == null); |
| super._local = scope._local; |
| super._setters = scope._setters; |
| super._parent = scope._parent; |
| super._extensions = scope._extensions; |
| } |
| |
| Scope createNestedScope(String debugName, {bool isModifiable: true}) { |
| return new Scope.nested(this, debugName, isModifiable: isModifiable); |
| } |
| |
| Scope withTypeVariables(List<TypeVariableBuilder>? typeVariables) { |
| if (typeVariables == null) return this; |
| Scope newScope = |
| new Scope.nested(this, "type variables", isModifiable: false); |
| for (TypeVariableBuilder t in typeVariables) { |
| newScope._local[t.name] = t; |
| } |
| return newScope; |
| } |
| |
| /// Create a special scope for use by labeled statements. This scope doesn't |
| /// introduce a new scope for local variables, only for labels. This deals |
| /// with corner cases like this: |
| /// |
| /// L: var x; |
| /// x = 42; |
| /// print("The answer is $x."); |
| Scope createNestedLabelScope() { |
| return new Scope( |
| local: _local, |
| setters: _setters, |
| extensions: _extensions, |
| parent: _parent, |
| debugName: "label", |
| isModifiable: true); |
| } |
| |
| void recordUse(String name, int charOffset) { |
| if (isModifiable) { |
| usedNames ??= <String, int>{}; |
| // Don't use putIfAbsent to avoid the context allocation needed |
| // for the closure. |
| usedNames![name] ??= charOffset; |
| } |
| } |
| |
| Builder? lookupIn(String name, int charOffset, Uri fileUri, |
| Map<String, Builder> map, bool isInstanceScope) { |
| Builder? builder = map[name]; |
| if (builder == null) return null; |
| if (builder.next != null) { |
| return new AmbiguousBuilder(name.isEmpty ? classNameOrDebugName : name, |
| builder, charOffset, fileUri); |
| } else if (!isInstanceScope && builder.isDeclarationInstanceMember) { |
| return null; |
| } else if (builder is MemberBuilder && builder.isConflictingSetter) { |
| // TODO(johnniwinther): Use a variant of [AmbiguousBuilder] for this case. |
| return null; |
| } else { |
| return builder; |
| } |
| } |
| |
| Builder? lookup(String name, int charOffset, Uri fileUri, |
| {bool isInstanceScope: true}) { |
| recordUse(name, charOffset); |
| Builder? builder = |
| lookupIn(name, charOffset, fileUri, _local, isInstanceScope); |
| if (builder != null) return builder; |
| builder = lookupIn(name, charOffset, fileUri, _setters, isInstanceScope); |
| if (builder != null && !builder.hasProblem) { |
| return new AccessErrorBuilder(name, builder, charOffset, fileUri); |
| } |
| if (!isInstanceScope) { |
| // For static lookup, do not search the parent scope. |
| return builder; |
| } |
| return builder ?? _parent?.lookup(name, charOffset, fileUri); |
| } |
| |
| Builder? lookupSetter(String name, int charOffset, Uri fileUri, |
| {bool isInstanceScope: true}) { |
| recordUse(name, charOffset); |
| Builder? builder = |
| lookupIn(name, charOffset, fileUri, _setters, isInstanceScope); |
| if (builder != null) return builder; |
| builder = lookupIn(name, charOffset, fileUri, _local, isInstanceScope); |
| if (builder != null && !builder.hasProblem) { |
| return new AccessErrorBuilder(name, builder, charOffset, fileUri); |
| } |
| if (!isInstanceScope) { |
| // For static lookup, do not search the parent scope. |
| return builder; |
| } |
| return builder ?? _parent?.lookupSetter(name, charOffset, fileUri); |
| } |
| |
| Builder? lookupLocalMember(String name, {required bool setter}) { |
| return setter ? _setters[name] : _local[name]; |
| } |
| |
| void addLocalMember(String name, Builder member, {required bool setter}) { |
| if (setter) { |
| _setters[name] = member as MemberBuilder; |
| } else { |
| _local[name] = member; |
| } |
| } |
| |
| void forEachLocalMember(void Function(String name, Builder member) f) { |
| _local.forEach(f); |
| } |
| |
| void forEachLocalSetter(void Function(String name, MemberBuilder member) f) { |
| _setters.forEach(f); |
| } |
| |
| Iterable<Builder> get localMembers => _local.values; |
| |
| Iterable<MemberBuilder> get localSetters => _setters.values; |
| |
| bool hasLocalLabel(String name) => |
| labels != null && labels!.containsKey(name); |
| |
| void declareLabel(String name, JumpTarget target) { |
| if (isModifiable) { |
| labels ??= <String, JumpTarget>{}; |
| labels![name] = target; |
| } else { |
| internalProblem( |
| messageInternalProblemExtendingUnmodifiableScope, -1, null); |
| } |
| } |
| |
| void forwardDeclareLabel(String name, JumpTarget target) { |
| declareLabel(name, target); |
| forwardDeclaredLabels ??= <String, JumpTarget>{}; |
| forwardDeclaredLabels![name] = target; |
| } |
| |
| bool claimLabel(String name) { |
| if (forwardDeclaredLabels == null || |
| forwardDeclaredLabels!.remove(name) == null) { |
| return false; |
| } |
| if (forwardDeclaredLabels!.length == 0) { |
| forwardDeclaredLabels = null; |
| } |
| return true; |
| } |
| |
| Map<String, JumpTarget>? get unclaimedForwardDeclarations { |
| return forwardDeclaredLabels; |
| } |
| |
| Builder? lookupLabel(String name) { |
| return labels?[name] ?? _parent?.lookupLabel(name); |
| } |
| |
| /// Declares that the meaning of [name] in this scope is [builder]. |
| /// |
| /// If name was used previously in this scope, this method returns a message |
| /// that can be used as context for reporting a compile-time error about |
| /// [name] being used before its declared. [fileUri] is used to bind the |
| /// location of this message. |
| LocatedMessage? declare(String name, Builder builder, Uri fileUri) { |
| if (isModifiable) { |
| int? offset = usedNames?[name]; |
| if (offset != null) { |
| return templateDuplicatedNamePreviouslyUsedCause |
| .withArguments(name) |
| .withLocation(fileUri, offset, name.length); |
| } |
| _local[name] = builder; |
| } else { |
| internalProblem( |
| messageInternalProblemExtendingUnmodifiableScope, -1, null); |
| } |
| return null; |
| } |
| |
| /// Adds [builder] to the extensions in this scope. |
| void addExtension(ExtensionBuilder builder) { |
| _extensions ??= <ExtensionBuilder>{}; |
| _extensions!.add(builder); |
| } |
| |
| /// Calls [f] for each extension in this scope and parent scopes. |
| void forEachExtension(void Function(ExtensionBuilder) f) { |
| _extensions?.forEach(f); |
| _parent?.forEachExtension(f); |
| } |
| |
| void merge( |
| Scope scope, |
| Builder computeAmbiguousDeclaration( |
| String name, Builder existing, Builder member)) { |
| Map<String, Builder> map = _local; |
| |
| void mergeMember(String name, Builder member) { |
| Builder? existing = map[name]; |
| if (existing != null) { |
| if (existing != member) { |
| member = computeAmbiguousDeclaration(name, existing, member); |
| } |
| } |
| map[name] = member; |
| } |
| |
| scope._local.forEach(mergeMember); |
| map = _setters; |
| scope._setters.forEach(mergeMember); |
| if (scope._extensions != null) { |
| (_extensions ??= {}).addAll(scope._extensions!); |
| } |
| } |
| |
| void forEach(f(String name, Builder member)) { |
| _local.forEach(f); |
| _setters.forEach(f); |
| } |
| |
| String get debugString { |
| StringBuffer buffer = new StringBuffer(); |
| int nestingLevel = writeOn(buffer); |
| for (int i = nestingLevel; i >= 0; i--) { |
| buffer.writeln("${' ' * i}}"); |
| } |
| return "$buffer"; |
| } |
| |
| int writeOn(StringSink sink) { |
| int nestingLevel = (_parent?.writeOn(sink) ?? -1) + 1; |
| String indent = " " * nestingLevel; |
| sink.writeln("$indent{"); |
| _local.forEach((String name, Builder member) { |
| sink.writeln("$indent $name"); |
| }); |
| _setters.forEach((String name, Builder member) { |
| sink.writeln("$indent $name="); |
| }); |
| return nestingLevel; |
| } |
| |
| Scope computeMixinScope() { |
| Map<String, Builder> local = <String, Builder>{}; |
| bool needsCopy = false; |
| for (MapEntry<String, Builder> entry in _local.entries) { |
| String name = entry.key; |
| Builder declaration = entry.value; |
| if (declaration.isStatic) { |
| needsCopy = true; |
| } else { |
| local[name] = declaration; |
| } |
| } |
| Map<String, MemberBuilder> setters = <String, MemberBuilder>{}; |
| for (MapEntry<String, MemberBuilder> entry in _setters.entries) { |
| String name = entry.key; |
| MemberBuilder declaration = entry.value; |
| if (declaration.isStatic) { |
| needsCopy = true; |
| } else { |
| setters[name] = declaration; |
| } |
| } |
| return needsCopy |
| ? new Scope( |
| local: local, |
| setters: setters, |
| extensions: _extensions, |
| parent: _parent, |
| debugName: classNameOrDebugName, |
| isModifiable: isModifiable) |
| : this; |
| } |
| } |
| |
| class ConstructorScope { |
| /// Constructors declared in this scope. |
| final Map<String, MemberBuilder> local; |
| |
| final String className; |
| |
| ConstructorScope(this.className, this.local); |
| |
| void forEach(f(String name, MemberBuilder member)) { |
| local.forEach(f); |
| } |
| |
| MemberBuilder? lookup(String name, int charOffset, Uri fileUri) { |
| MemberBuilder? builder = local[name]; |
| if (builder == null) return null; |
| if (builder.next != null) { |
| return new AmbiguousMemberBuilder( |
| name.isEmpty ? className : name, builder, charOffset, fileUri); |
| } else { |
| return builder; |
| } |
| } |
| |
| @override |
| String toString() => "ConstructorScope($className, ${local.keys})"; |
| } |
| |
| abstract class LazyScope extends Scope { |
| LazyScope(Map<String, Builder> local, Map<String, MemberBuilder> setters, |
| Scope? parent, String debugName, {bool isModifiable: true}) |
| : super( |
| local: local, |
| setters: setters, |
| parent: parent, |
| debugName: debugName, |
| isModifiable: isModifiable); |
| |
| /// Override this method to lazily populate the scope before access. |
| void ensureScope(); |
| |
| @override |
| Map<String, Builder> get _local { |
| ensureScope(); |
| return super._local; |
| } |
| |
| @override |
| Map<String, MemberBuilder> get _setters { |
| ensureScope(); |
| return super._setters; |
| } |
| |
| @override |
| Set<ExtensionBuilder>? get _extensions { |
| ensureScope(); |
| return super._extensions; |
| } |
| } |
| |
| class ScopeBuilder { |
| final Scope scope; |
| |
| ScopeBuilder(this.scope); |
| |
| void addMember(String name, Builder builder) { |
| scope._local[name] = builder; |
| } |
| |
| void addSetter(String name, MemberBuilder builder) { |
| scope._setters[name] = builder; |
| } |
| |
| void addExtension(ExtensionBuilder builder) { |
| scope.addExtension(builder); |
| } |
| |
| Builder? operator [](String name) => scope._local[name]; |
| } |
| |
| class ConstructorScopeBuilder { |
| final ConstructorScope scope; |
| |
| ConstructorScopeBuilder(this.scope); |
| |
| void addMember(String name, MemberBuilder builder) { |
| scope.local[name] = builder; |
| } |
| |
| MemberBuilder? operator [](String name) => scope.local[name]; |
| } |
| |
| abstract class ProblemBuilder extends BuilderImpl { |
| final String name; |
| |
| final Builder builder; |
| |
| @override |
| final int charOffset; |
| |
| @override |
| final Uri fileUri; |
| |
| ProblemBuilder(this.name, this.builder, this.charOffset, this.fileUri); |
| |
| @override |
| bool get hasProblem => true; |
| |
| Message get message; |
| |
| @override |
| String get fullNameForErrors => name; |
| } |
| |
| /// Represents a [builder] that's being accessed incorrectly. For example, an |
| /// attempt to write to a final field, or to read from a setter. |
| class AccessErrorBuilder extends ProblemBuilder { |
| AccessErrorBuilder(String name, Builder builder, int charOffset, Uri fileUri) |
| : super(name, builder, charOffset, fileUri); |
| |
| @override |
| Builder get parent => builder; |
| |
| @override |
| bool get isFinal => builder.isFinal; |
| |
| @override |
| bool get isField => builder.isField; |
| |
| @override |
| bool get isRegularMethod => builder.isRegularMethod; |
| |
| @override |
| bool get isGetter => !builder.isGetter; |
| |
| @override |
| bool get isSetter => !builder.isSetter; |
| |
| @override |
| bool get isDeclarationInstanceMember => builder.isDeclarationInstanceMember; |
| |
| @override |
| bool get isClassInstanceMember => builder.isClassInstanceMember; |
| |
| @override |
| bool get isExtensionInstanceMember => builder.isExtensionInstanceMember; |
| |
| @override |
| bool get isStatic => builder.isStatic; |
| |
| @override |
| bool get isTopLevel => builder.isTopLevel; |
| |
| @override |
| bool get isTypeDeclaration => builder.isTypeDeclaration; |
| |
| @override |
| bool get isLocal => builder.isLocal; |
| |
| @override |
| Message get message => templateAccessError.withArguments(name); |
| } |
| |
| class AmbiguousBuilder extends ProblemBuilder { |
| AmbiguousBuilder(String name, Builder builder, int charOffset, Uri fileUri) |
| : super(name, builder, charOffset, fileUri); |
| |
| @override |
| Builder? get parent => null; |
| |
| @override |
| Message get message => templateDuplicatedDeclarationUse.withArguments(name); |
| |
| // TODO(ahe): Also provide context. |
| |
| Builder getFirstDeclaration() { |
| Builder declaration = builder; |
| while (declaration.next != null) { |
| declaration = declaration.next!; |
| } |
| return declaration; |
| } |
| } |
| |
| mixin ErroneousMemberBuilderMixin implements SourceMemberBuilder { |
| @override |
| MemberDataForTesting? get dataForTesting => null; |
| |
| @override |
| Member get member => throw new UnsupportedError('$runtimeType.member'); |
| |
| @override |
| Member? get readTarget => null; |
| |
| @override |
| Member? get writeTarget => null; |
| |
| @override |
| Member? get invokeTarget => null; |
| |
| @override |
| Iterable<Member> get exportedMembers => const []; |
| |
| @override |
| bool get isNative => false; |
| |
| @override |
| bool get isAssignable => false; |
| |
| @override |
| bool get isExternal => false; |
| |
| @override |
| bool get isAbstract => false; |
| |
| @override |
| bool get isConflictingSetter => false; |
| |
| @override |
| void set parent(Builder? value) { |
| throw new UnsupportedError('AmbiguousMemberBuilder.parent='); |
| } |
| |
| @override |
| LibraryBuilder get library { |
| throw new UnsupportedError('AmbiguousMemberBuilder.parent='); |
| } |
| |
| // TODO(johnniwinther): Remove this and create a [ProcedureBuilder] interface. |
| @override |
| ProcedureKind? get kind => null; |
| |
| @override |
| void buildOutlineExpressions( |
| SourceLibraryBuilder library, |
| ClassHierarchy classHierarchy, |
| List<DelayedActionPerformer> delayedActionPerformers, |
| List<SynthesizedFunctionNode> synthesizedFunctionNodes) { |
| throw new UnsupportedError( |
| 'AmbiguousMemberBuilder.buildOutlineExpressions'); |
| } |
| |
| @override |
| void buildMembers( |
| SourceLibraryBuilder library, void Function(Member, BuiltMemberKind) f) { |
| assert(false, "Unexpected call to $runtimeType.buildMembers."); |
| } |
| |
| @override |
| List<ClassMember> get localMembers => const <ClassMember>[]; |
| |
| @override |
| List<ClassMember> get localSetters => const <ClassMember>[]; |
| } |
| |
| class AmbiguousMemberBuilder extends AmbiguousBuilder |
| with ErroneousMemberBuilderMixin { |
| AmbiguousMemberBuilder( |
| String name, Builder builder, int charOffset, Uri fileUri) |
| : super(name, builder, charOffset, fileUri); |
| } |
| |
| class ScopeLocalDeclarationIterator implements Iterator<Builder> { |
| Iterator<Builder>? local; |
| final Iterator<Builder> setters; |
| |
| Builder? _current; |
| |
| ScopeLocalDeclarationIterator(Scope scope) |
| : local = scope._local.values.iterator, |
| setters = scope._setters.values.iterator; |
| |
| @override |
| bool moveNext() { |
| Builder? next = _current?.next; |
| if (next != null) { |
| _current = next; |
| return true; |
| } |
| if (local != null) { |
| if (local!.moveNext()) { |
| _current = local!.current; |
| return true; |
| } |
| local = null; |
| } |
| if (setters.moveNext()) { |
| _current = setters.current; |
| return true; |
| } else { |
| _current = null; |
| return false; |
| } |
| } |
| |
| @override |
| Builder get current { |
| return _current ?? (throw new StateError('No element')); |
| } |
| } |
| |
| class ScopeLocalDeclarationNameIterator extends ScopeLocalDeclarationIterator |
| implements NameIterator { |
| Iterator<String>? localNames; |
| final Iterator<String> setterNames; |
| |
| String? _name; |
| |
| ScopeLocalDeclarationNameIterator(Scope scope) |
| : localNames = scope._local.keys.iterator, |
| setterNames = scope._setters.keys.iterator, |
| super(scope); |
| |
| @override |
| bool moveNext() { |
| Builder? next = _current?.next; |
| if (next != null) { |
| _current = next; |
| return true; |
| } |
| if (local != null) { |
| if (local!.moveNext()) { |
| localNames!.moveNext(); |
| _current = local!.current; |
| _name = localNames!.current; |
| return true; |
| } |
| localNames = null; |
| } |
| if (setters.moveNext()) { |
| setterNames.moveNext(); |
| _current = setters.current; |
| _name = setterNames.current; |
| return true; |
| } else { |
| _current = null; |
| return false; |
| } |
| } |
| |
| @override |
| String get name { |
| return _name ?? (throw new StateError('No element')); |
| } |
| } |