blob: a27d016f4bec41c8544c92c40368100cbc40679b [file] [log] [blame]
// 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.
// ignore_for_file: annotate_overrides
library fasta.scope;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/type_environment.dart';
import '../builder/builder.dart';
import '../builder/declaration_builders.dart';
import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/metadata_builder.dart';
import '../builder/name_iterator.dart';
import '../builder/prefix_builder.dart';
import '../kernel/body_builder.dart' show JumpTarget;
import '../kernel/body_builder_context.dart';
import '../kernel/hierarchy/class_member.dart' show ClassMember;
import '../kernel/kernel_helper.dart';
import '../kernel/load_library_builder.dart';
import '../source/source_class_builder.dart';
import '../source/source_extension_builder.dart';
import '../source/source_extension_type_declaration_builder.dart';
import '../source/source_function_builder.dart';
import '../source/source_library_builder.dart';
import '../source/source_member_builder.dart';
import '../util/helpers.dart' show DelayedActionPerformer;
import 'messages.dart';
import 'uri_offset.dart';
enum ScopeKind {
/// Scope of pattern switch-case statements
///
/// These scopes receive special treatment in that they are end-points of the
/// scope stack in presence of multiple heads for the same case, but can have
/// nested scopes if it's just a single head. In that latter possibility the
/// body of the case is nested into the scope of the case head. And for switch
/// expressions that scope includes both the head and the case expression.
caseHead,
/// The declaration-level scope for classes, enums, and similar declarations
declaration,
/// Scope where the formal parameters of a function are declared
formals,
/// Scope of a `for` statement
forStatement,
/// Scope of a function body
functionBody,
/// Scope of the head of the if-case statement
ifCaseHead,
/// Scope of an if-element in a collection
ifElement,
/// Scope for the initializers of generative constructors
initializers,
/// Scope where the joint variables of a switch case are declared
jointVariables,
/// Scope where labels of labelled statements are declared
labels,
/// Top-level scope of a library
library,
/// The special scope of the named function expression
///
/// This scope is treated separately because the named function expressions
/// are allowed to be recursive, and the name of that function expression
/// should be visible in the scope of the function itself.
namedFunctionExpression,
/// The scope of the RHS of a binary-or pattern
///
/// It is utilized for separating the branch-local variables from the joint
/// variables of the overall binary-or pattern.
orPatternRight,
/// The scope of a pattern
///
/// It contains the variables associated with pattern variable declarations.
pattern,
/// Local scope of a statement, such as the body of a while loop
statementLocalScope,
/// Local scope of a switch block
switchBlock,
/// Scope for switch cases
///
/// This scope kind is used in assertion checks.
switchCase,
/// Scope for switch case bodies
///
/// This is used to handle local variables of switch cases.
switchCaseBody,
/// Scope for type parameters of declarations
typeParameters,
}
abstract class LookupScope {
ScopeKind get kind;
Builder? lookup(String name, int charOffset, Uri fileUri);
Builder? lookupSetter(String name, int charOffset, Uri fileUri);
}
mixin LookupScopeMixin implements LookupScope {
String get classNameOrDebugName;
Builder? lookupIn(
String name, int charOffset, Uri fileUri, Map<String, Builder> map) {
Builder? builder = map[name];
if (builder == null) return null;
if (builder.next != null) {
return new AmbiguousBuilder(
name.isEmpty
?
// Coverage-ignore(suite): Not run.
classNameOrDebugName
: name,
builder,
charOffset,
fileUri);
} else if (builder is MemberBuilder && builder.isConflictingSetter) {
// TODO(johnniwinther): Use a variant of [AmbiguousBuilder] for this case.
return null;
} else {
return builder;
}
}
Builder? lookupSetterIn(
String name, int charOffset, Uri fileUri, Map<String, Builder>? map) {
Builder? builder;
if (map != null) {
builder = lookupIn(name, charOffset, fileUri, map);
if (builder != null && !builder.hasProblem) {
return new AccessErrorBuilder(name, builder, charOffset, fileUri);
}
}
return builder;
}
}
class TypeParameterScope with LookupScopeMixin {
final LookupScope _parent;
final Map<String, Builder> _typeParameters;
TypeParameterScope(this._parent, this._typeParameters);
@override
ScopeKind get kind => ScopeKind.typeParameters;
@override
Builder? lookup(String name, int charOffset, Uri fileUri) {
return lookupIn(name, charOffset, fileUri, _typeParameters) ??
_parent.lookup(name, charOffset, fileUri);
}
@override
Builder? lookupSetter(String name, int charOffset, Uri fileUri) {
Builder? builder =
lookupSetterIn(name, charOffset, fileUri, _typeParameters);
return builder ?? _parent.lookupSetter(name, charOffset, fileUri);
}
@override
String get classNameOrDebugName => "type parameter";
}
abstract class ParentScope {
ParentScope? get parent;
void forEachExtension(void Function(ExtensionBuilder) f);
Builder? lookup(String name, int charOffset, Uri fileUri);
Builder? lookupSetter(String name, int charOffset, Uri fileUri);
int writeOn(StringSink sink);
}
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.
ParentScope? _parent;
final String classNameOrDebugName;
final ScopeKind kind;
MutableScope(this.kind, this._local, this._setters, this._extensions,
this._parent, this.classNameOrDebugName);
ParentScope? get parent => _parent;
@override
String toString() => "Scope(${kind}, $classNameOrDebugName, ${_local?.keys})";
}
abstract class NameSpace {
void addLocalMember(String name, Builder member, {required bool setter});
Builder? lookupLocalMember(String name, {required bool setter});
void forEachLocalMember(void Function(String name, Builder member) f);
void forEachLocalSetter(void Function(String name, MemberBuilder member) f);
void forEachLocalExtension(void Function(ExtensionBuilder member) f);
Iterable<Builder> get localMembers;
/// Returns an iterator of all members and setters mapped in this name space,
/// including duplicate members mapped to the same name.
Iterator<Builder> get unfilteredIterator;
/// Returns an iterator of all members and setters mapped in this name space,
/// including duplicate members mapped to the same name.
///
/// Compared to [unfilteredIterator] this iterator also gives access to the
/// name that the builders are mapped to.
NameIterator get unfilteredNameIterator;
/// 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. If [includeDuplicates] is `true`, all
/// duplicates of the same name are included, otherwise, only the first
/// declared member is included. If [includeAugmentations] is `true`, both
/// original and augmenting/patching members are included, otherwise, only
/// original members are included.
Iterator<T> filteredIterator<T extends Builder>(
{Builder? parent,
required bool includeDuplicates,
required bool includeAugmentations});
/// 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. If [includeDuplicates] is `true`, all
/// duplicates of the same name are included, otherwise, only the first
/// declared member is included. If [includeAugmentations] is `true`, both
/// original and augmenting/patching members are included, otherwise, only
/// original members are included.
///
/// Compared to [filteredIterator] this iterator also gives access to the
/// name that the builders are mapped to.
NameIterator<T> filteredNameIterator<T extends Builder>(
{Builder? parent,
required bool includeDuplicates,
required bool includeAugmentations});
}
class Scope extends MutableScope
implements ParentScope, LookupScope, NameSpace {
/// 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, List<Builder>>? augmentations;
Map<String, List<Builder>>? setterAugmentations;
Scope(
{required ScopeKind kind,
Map<String, Builder>? local,
Map<String, MemberBuilder>? setters,
Set<ExtensionBuilder>? extensions,
ParentScope? parent,
this.augmentations,
this.setterAugmentations,
required String debugName,
this.isModifiable = true})
: super(kind, local, setters, extensions, parent, debugName);
Scope.top({required ScopeKind kind, bool isModifiable = false})
: this(
kind: kind,
local: <String, Builder>{},
setters: <String, MemberBuilder>{},
debugName: "top",
isModifiable: isModifiable);
Scope.nested(Scope parent, String debugName,
{bool isModifiable = true,
required ScopeKind kind,
Map<String, Builder>? local})
: this(
kind: kind,
parent: parent,
debugName: debugName,
isModifiable: isModifiable,
local: local);
/// Returns an iterator of all members and setters mapped in this scope,
/// including duplicate members mapped to the same name.
///
/// The iterator does _not_ include the members and setters mapped in the
/// [parent] scope.
Iterator<Builder> get unfilteredIterator {
return new ScopeIterator(this);
}
/// Returns an iterator of all members and setters mapped in this scope,
/// including duplicate members mapped to the same name.
///
/// The iterator does _not_ include the members and setters mapped in the
/// [parent] scope.
///
/// Compared to [unfilteredIterator] this iterator also gives access to the
/// name that the builders are mapped to.
NameIterator get unfilteredNameIterator {
return new ScopeNameIterator(this);
}
/// Returns a filtered iterator of members and setters mapped in this scope.
///
/// Only members of type [T] are included. If [parent] is provided, on members
/// declared in [parent] are included. If [includeDuplicates] is `true`, all
/// duplicates of the same name are included, otherwise, only the first
/// declared member is included. If [includeAugmentations] is `true`, both
/// original and augmenting/patching members are included, otherwise, only
/// original members are included.
Iterator<T> filteredIterator<T extends Builder>(
{Builder? parent,
required bool includeDuplicates,
required bool includeAugmentations}) {
return new FilteredIterator<T>(unfilteredIterator,
parent: parent,
includeDuplicates: includeDuplicates,
includeAugmentations: includeAugmentations);
}
/// Returns a filtered iterator of members and setters mapped in this scope.
///
/// Only members of type [T] are included. If [parent] is provided, on members
/// declared in [parent] are included. If [includeDuplicates] is `true`, all
/// duplicates of the same name are included, otherwise, only the first
/// declared member is included. If [includeAugmentations] is `true`, both
/// original and augmenting/patching members are included, otherwise, only
/// original members are included.
///
/// Compared to [filteredIterator] this iterator also gives access to the
/// name that the builders are mapped to.
NameIterator<T> filteredNameIterator<T extends Builder>(
{Builder? parent,
required bool includeDuplicates,
required bool includeAugmentations}) {
return new FilteredNameIterator<T>(unfilteredNameIterator,
parent: parent,
includeDuplicates: includeDuplicates,
includeAugmentations: includeAugmentations);
}
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 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) {
// Coverage-ignore-block(suite): Not run.
// 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 ??= // Coverage-ignore(suite): Not run.
{})[name] = replacementGetterFromGetter;
} else {
// Coverage-ignore-block(suite): Not run.
// 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 ??= // Coverage-ignore(suite): Not run.
{})[name] = replacementSetterFromGetter as MemberBuilder;
}
} else if (existingSetter.parent ==
replacementLibraryBuilderFromSetter) {
// The existing setter should be replaced.
if (replacementSetterFromSetter != null) {
// With a new setter.
(_setters ??= // Coverage-ignore(suite): Not run.
{})[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) {
// Coverage-ignore-block(suite): Not run.
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);
}
}
}
// Coverage-ignore(suite): Not run.
Scope copyWithParent(Scope parent, String debugName) {
return new Scope(
kind: kind,
local: super._local,
setters: super._setters,
extensions: _extensions,
parent: parent,
debugName: debugName,
isModifiable: isModifiable);
}
/// 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(
{required String debugName,
bool isModifiable = true,
required ScopeKind kind,
Map<String, Builder>? local}) {
return new Scope.nested(this, debugName,
isModifiable: isModifiable, kind: kind, local: local);
}
Scope withTypeVariables(List<NominalVariableBuilder>? typeVariables) {
if (typeVariables == null) return this;
Scope newScope = new Scope.nested(this, "type variables",
isModifiable: false, kind: ScopeKind.typeParameters);
for (NominalVariableBuilder t in typeVariables) {
if (t.isWildcard) continue;
(newScope._local ??= {})[t.name] = t;
}
return newScope;
}
Scope withStructuralVariables(
List<StructuralVariableBuilder>? typeVariables) {
if (typeVariables == null) return this;
Scope newScope = new Scope.nested(this, "type variables",
isModifiable: false, kind: ScopeKind.typeParameters);
for (StructuralVariableBuilder t in typeVariables) {
(newScope._local ??= {})[t.name] = t;
}
return newScope;
}
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
?
// Coverage-ignore(suite): Not run.
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;
}
}
/// Lookup a member with [name] in the scope.
@override
Builder? lookup(String name, int charOffset, Uri fileUri,
{bool isInstanceScope = true}) {
Builder? builder;
if (_local != null) {
builder = lookupIn(name, charOffset, fileUri, _local!, isInstanceScope);
if (builder != null) return builder;
}
if (_setters != null) {
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);
}
@override
Builder? lookupSetter(String name, int charOffset, Uri fileUri,
{bool isInstanceScope = true}) {
Builder? builder;
if (_setters != null) {
builder = lookupIn(name, charOffset, fileUri, _setters!, isInstanceScope);
if (builder != null) return builder;
}
if (_local != null) {
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 ??= // Coverage-ignore(suite): Not run.
{})[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);
}
void forEachLocalExtension(void Function(ExtensionBuilder member) f) {
_extensions?.forEach(f);
}
Iterable<Builder> get localMembers => _local?.values ?? const {};
/// 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.
@override
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 = const {};
void mergeMember(String name, Builder member) {
Builder? existing = map[name];
if (existing != null) {
if (existing != member) {
member = computeAmbiguousDeclaration(name, existing, member);
}
}
map[name] = member;
}
if (scope._local != null) {
map = _local ??= // Coverage-ignore(suite): Not run.
{};
scope._local?.forEach(mergeMember);
}
if (scope._setters != null) {
map = _setters ??= // Coverage-ignore(suite): Not run.
{};
scope._setters?.forEach(mergeMember);
}
if (scope._extensions != null) {
(_extensions ??= {}).addAll(scope._extensions!);
}
}
String get debugString {
StringBuffer buffer = new StringBuffer();
int nestingLevel = writeOn(buffer);
for (int i = nestingLevel; i >= 0; i--) {
buffer.writeln("${' ' * i}}");
}
return "$buffer";
}
// Coverage-ignore(suite): Not run.
@override
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;
}
}
class ConstructorScope {
/// Constructors declared in this scope.
final Map<String, MemberBuilder> _local;
final String className;
ConstructorScope(this.className, this._local);
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;
}
}
MemberBuilder? lookupLocalMember(String name) {
return _local[name];
}
void addLocalMember(String name, MemberBuilder builder) {
_local[name] = builder;
}
void addLocalMembers(Map<String, MemberBuilder> map) {
_local.addAll(map);
}
/// Returns an iterator of all constructors mapped in this scope,
/// including duplicate constructors mapped to the same name.
Iterator<MemberBuilder> get unfilteredIterator =>
new ConstructorScopeIterator(this);
/// Returns an iterator of all constructors mapped in this scope,
/// including duplicate constructors mapped to the same name.
///
/// Compared to [unfilteredIterator] this iterator also gives access to the
/// name that the builders are mapped to.
NameIterator<MemberBuilder> get unfilteredNameIterator =>
new ConstructorScopeNameIterator(this);
/// Returns a filtered iterator of constructors mapped in this scope.
///
/// Only members of type [T] are included. If [parent] is provided, on members
/// declared in [parent] are included. If [includeDuplicates] is `true`, all
/// duplicates of the same name are included, otherwise, only the first
/// declared member is included. If [includeAugmentations] is `true`, both
/// original and augmenting/patching members are included, otherwise, only
/// original members are included.
Iterator<T> filteredIterator<T extends MemberBuilder>(
{Builder? parent,
required bool includeDuplicates,
required bool includeAugmentations}) {
return new FilteredIterator<T>(unfilteredIterator,
parent: parent,
includeDuplicates: includeDuplicates,
includeAugmentations: includeAugmentations);
}
/// Returns a filtered iterator of constructors mapped in this scope.
///
/// Only members of type [T] are included. If [parent] is provided, on members
/// declared in [parent] are included. If [includeDuplicates] is `true`, all
/// duplicates of the same name are included, otherwise, only the first
/// declared member is included. If [includeAugmentations] is `true`, both
/// original and augmenting/patching members are included, otherwise, only
/// original members are included.
///
/// Compared to [filteredIterator] this iterator also gives access to the
/// name that the builders are mapped to.
NameIterator<T> filteredNameIterator<T extends MemberBuilder>(
{Builder? parent,
required bool includeDuplicates,
required bool includeAugmentations}) {
return new FilteredNameIterator<T>(unfilteredNameIterator,
parent: parent,
includeDuplicates: includeDuplicates,
includeAugmentations: includeAugmentations);
}
@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, required ScopeKind kind})
: super(
kind: kind,
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;
}
}
/// Computes a builder for the import/export collision between [declaration] and
/// [other] and adds it to [scope].
Builder computeAmbiguousDeclarationForScope(ProblemReporting problemReporting,
Scope scope, String name, Builder declaration, Builder other,
{required UriOffset uriOffset,
bool isExport = false,
bool isImport = false}) {
// TODO(ahe): Can I move this to Scope or Prefix?
if (declaration == other) return declaration;
if (declaration is InvalidTypeDeclarationBuilder) return declaration;
if (other is InvalidTypeDeclarationBuilder) return other;
if (declaration is AccessErrorBuilder) {
// Coverage-ignore-block(suite): Not run.
AccessErrorBuilder error = declaration;
declaration = error.builder;
}
if (other is AccessErrorBuilder) {
// Coverage-ignore-block(suite): Not run.
AccessErrorBuilder error = other;
other = error.builder;
}
Builder? preferred;
Uri? uri;
Uri? otherUri;
if (scope.lookupLocalMember(name, setter: false) == declaration) {
preferred = declaration;
} else {
uri = computeLibraryUri(declaration);
otherUri = computeLibraryUri(other);
if (declaration is LoadLibraryBuilder) {
preferred = declaration;
} else if (other is LoadLibraryBuilder) {
preferred = other;
} else if (otherUri.isScheme("dart") && !uri.isScheme("dart")) {
preferred = declaration;
} else if (uri.isScheme("dart") && !otherUri.isScheme("dart")) {
preferred = other;
}
}
if (preferred != null) {
return preferred;
}
if (declaration.next == null && other.next == null) {
if (isImport &&
declaration is PrefixBuilder &&
// Coverage-ignore(suite): Not run.
other is PrefixBuilder) {
// Coverage-ignore-block(suite): Not run.
// Handles the case where the same prefix is used for different
// imports.
declaration.mergeScopes(other, problemReporting, scope,
uriOffset: uriOffset, isImport: isImport, isExport: isExport);
return declaration;
}
}
Uri firstUri = uri!;
Uri secondUri = otherUri!;
if (firstUri.toString().compareTo(secondUri.toString()) > 0) {
firstUri = secondUri;
secondUri = uri;
}
if (isExport) {
Template<Message Function(String name, Uri uri, Uri uri2)> template =
templateDuplicatedExport;
Message message = template.withArguments(name, firstUri, secondUri);
problemReporting.addProblem(
message, uriOffset.fileOffset, noLength, uriOffset.uri);
}
Template<Message Function(String name, Uri uri, Uri uri2)> builderTemplate =
isExport
? templateDuplicatedExportInType
: templateDuplicatedImportInType;
Message message = builderTemplate.withArguments(
name,
// TODO(ahe): We should probably use a context object here
// instead of including URIs in this message.
firstUri,
secondUri);
// We report the error lazily (setting suppressMessage to false) because the
// spec 18.1 states that 'It is not an error if N is introduced by two or
// more imports but never referred to.'
return new InvalidTypeDeclarationBuilder(name,
message.withLocation(uriOffset.uri, uriOffset.fileOffset, name.length),
suppressMessage: false);
}
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.parent;
@override
// Coverage-ignore(suite): Not run.
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 isExtensionTypeInstanceMember =>
builder.isExtensionTypeInstanceMember;
@override
bool get isStatic => builder.isStatic;
@override
// Coverage-ignore(suite): Not run.
bool get isTopLevel => builder.isTopLevel;
@override
bool get isTypeDeclaration => builder.isTypeDeclaration;
@override
bool get isLocal => builder.isLocal;
@override
// Coverage-ignore(suite): Not run.
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
// Coverage-ignore(suite): Not run.
Builder? get parent => null;
@override
Message get message => templateDuplicatedDeclarationUse.withArguments(name);
// Coverage-ignore(suite): Not run.
// TODO(ahe): Also provide context.
Builder getFirstDeclaration() {
Builder declaration = builder;
while (declaration.next != null) {
declaration = declaration.next!;
}
return declaration;
}
}
mixin ErroneousMemberBuilderMixin implements SourceMemberBuilder {
@override
// Coverage-ignore(suite): Not run.
MemberDataForTesting? get dataForTesting => null;
@override
// Coverage-ignore(suite): Not run.
Member get member => throw new UnsupportedError('$runtimeType.member');
@override
// Coverage-ignore(suite): Not run.
Name get memberName => throw new UnsupportedError('$runtimeType.memberName');
@override
// Coverage-ignore(suite): Not run.
Member? get readTarget => null;
@override
// Coverage-ignore(suite): Not run.
Member? get writeTarget => null;
@override
// Coverage-ignore(suite): Not run.
Member? get invokeTarget => null;
@override
// Coverage-ignore(suite): Not run.
Iterable<Member> get exportedMembers => const [];
@override
// Coverage-ignore(suite): Not run.
bool get isAssignable => false;
@override
// Coverage-ignore(suite): Not run.
bool get isExternal => false;
@override
// Coverage-ignore(suite): Not run.
bool get isAbstract => false;
@override
bool get isConflictingSetter => false;
@override
bool get isConflictingAugmentationMember => false;
@override
void set isConflictingAugmentationMember(bool value) {
throw new UnsupportedError('$runtimeType.isConflictingAugmentationMember=');
}
@override
void set parent(Builder? value) {
throw new UnsupportedError('$runtimeType.parent=');
}
@override
DeclarationBuilder get declarationBuilder {
throw new UnsupportedError('$runtimeType.declarationBuilder');
}
@override
ClassBuilder get classBuilder {
throw new UnsupportedError('$runtimeType.classBuilder');
}
@override
SourceLibraryBuilder get libraryBuilder {
throw new UnsupportedError('$runtimeType.library');
}
// TODO(johnniwinther): Remove this and create a [ProcedureBuilder] interface.
@override
// Coverage-ignore(suite): Not run.
ProcedureKind? get kind => null;
@override
void buildOutlineExpressions(
ClassHierarchy classHierarchy,
List<DelayedActionPerformer> delayedActionPerformers,
List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
throw new UnsupportedError('$runtimeType.buildOutlineExpressions');
}
@override
// Coverage-ignore(suite): Not run.
void buildOutlineNodes(BuildNodesCallback f) {
assert(false, "Unexpected call to $runtimeType.buildOutlineNodes.");
}
@override
// Coverage-ignore(suite): Not run.
int buildBodyNodes(BuildNodesCallback f) {
assert(false, "Unexpected call to $runtimeType.buildBodyNodes.");
return 0;
}
@override
// Coverage-ignore(suite): Not run.
List<ClassMember> get localMembers => const <ClassMember>[];
@override
// Coverage-ignore(suite): Not run.
List<ClassMember> get localSetters => const <ClassMember>[];
@override
// Coverage-ignore(suite): Not run.
void checkVariance(
SourceClassBuilder sourceClassBuilder, TypeEnvironment typeEnvironment) {
assert(false, "Unexpected call to $runtimeType.checkVariance.");
}
@override
// Coverage-ignore(suite): Not run.
void checkTypes(
SourceLibraryBuilder library, TypeEnvironment typeEnvironment) {
assert(false, "Unexpected call to $runtimeType.checkVariance.");
}
@override
bool get isAugmentation {
throw new UnsupportedError('$runtimeType.isAugmentation');
}
@override
AugmentSuperTarget? get augmentSuperTarget {
throw new UnsupportedError('$runtimeType.augmentSuperTarget}');
}
@override
BodyBuilderContext createBodyBuilderContext(
{required bool inOutlineBuildingPhase,
required bool inMetadata,
required bool inConstFields}) {
throw new UnsupportedError(
'$runtimeType.bodyBuilderContextForAnnotations}');
}
@override
Iterable<Annotatable> get annotatables {
throw new UnsupportedError('$runtimeType.annotatables}');
}
}
class AmbiguousMemberBuilder extends AmbiguousBuilder
with ErroneousMemberBuilderMixin {
AmbiguousMemberBuilder(
String name, Builder builder, int charOffset, Uri fileUri)
: super(name, builder, charOffset, fileUri);
}
/// Iterator over builders mapped in a [Scope], including duplicates for each
/// directly mapped builder.
class ScopeIterator implements Iterator<Builder> {
Iterator<Builder>? local;
Iterator<Builder>? setters;
Iterator<Builder>? extensions;
Builder? _current;
ScopeIterator.fromIterators(this.local, this.setters, this.extensions);
ScopeIterator(Scope scope)
: this.fromIterators(scope._local?.values.iterator,
scope._setters?.values.iterator, scope._extensions?.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 != null) {
if (setters!.moveNext()) {
_current = setters!.current;
return true;
}
setters = null;
}
if (extensions != null) {
while (extensions!.moveNext()) {
Builder extension = extensions!.current;
// Named extensions have already been included throw [local] so we skip
// them here.
if (extension is SourceExtensionBuilder &&
extension.isUnnamedExtension) {
_current = extension;
return true;
}
}
extensions = null;
}
_current = null;
return false;
}
@override
Builder get current {
return _current ?? // Coverage-ignore(suite): Not run.
(throw new StateError('No element'));
}
}
/// Iterator over builders mapped in a [Scope], including duplicates for each
/// directly mapped builder.
///
/// Compared to [ScopeIterator] this iterator also gives
/// access to the name that the builders are mapped to.
class ScopeNameIterator extends ScopeIterator implements NameIterator<Builder> {
Iterator<String>? localNames;
Iterator<String>? setterNames;
String? _name;
ScopeNameIterator(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;
}
local = null;
localNames = null;
}
if (setters != null) {
if (setters!.moveNext()) {
setterNames!.moveNext();
_current = setters!.current;
_name = setterNames!.current;
return true;
}
setters = null;
setterNames = null;
}
if (extensions != null) {
while (extensions!.moveNext()) {
Builder extension = extensions!.current;
// Named extensions have already been included throw [local] so we skip
// them here.
if (extension is SourceExtensionBuilder &&
extension.isUnnamedExtension) {
_current = extension;
_name = extension.name;
return true;
}
}
extensions = null;
}
_current = null;
_name = null;
return false;
}
@override
String get name {
return _name ?? // Coverage-ignore(suite): Not run.
(throw new StateError('No element'));
}
}
/// Iterator over builders mapped in a [ConstructorScope], including duplicates
/// for each directly mapped builder.
class ConstructorScopeIterator implements Iterator<MemberBuilder> {
Iterator<MemberBuilder> local;
MemberBuilder? _current;
ConstructorScopeIterator(ConstructorScope scope)
: local = scope._local.values.iterator;
@override
bool moveNext() {
MemberBuilder? next = _current?.next as MemberBuilder?;
if (next != null) {
_current = next;
return true;
}
if (local.moveNext()) {
_current = local.current;
return true;
}
return false;
}
@override
MemberBuilder get current {
return _current ?? // Coverage-ignore(suite): Not run.
(throw new StateError('No element'));
}
}
/// Iterator over builders mapped in a [ConstructorScope], including duplicates
/// for each directly mapped builder.
///
/// Compared to [ConstructorScopeIterator] this iterator also gives
/// access to the name that the builders are mapped to.
class ConstructorScopeNameIterator extends ConstructorScopeIterator
implements NameIterator<MemberBuilder> {
final Iterator<String> localNames;
String? _name;
ConstructorScopeNameIterator(ConstructorScope scope)
: localNames = scope._local.keys.iterator,
super(scope);
@override
bool moveNext() {
MemberBuilder? next = _current?.next as MemberBuilder?;
if (next != null) {
_current = next;
return true;
}
if (local.moveNext()) {
localNames.moveNext();
_current = local.current;
_name = localNames.current;
return true;
}
_current = null;
_name = null;
return false;
}
@override
String get name {
return _name ?? // Coverage-ignore(suite): Not run.
(throw new StateError('No element'));
}
}
/// Filtered builder [Iterator].
class FilteredIterator<T extends Builder> implements Iterator<T> {
final Iterator<Builder> _iterator;
final Builder? parent;
final bool includeDuplicates;
final bool includeAugmentations;
FilteredIterator(this._iterator,
{required this.parent,
required this.includeDuplicates,
required this.includeAugmentations});
bool _include(Builder element) {
if (parent != null && element.parent != parent) return false;
if (!includeDuplicates &&
(element.isDuplicate || element.isConflictingAugmentationMember)) {
return false;
}
if (!includeAugmentations && element.isAugmenting) return false;
return element is T;
}
@override
T get current => _iterator.current as T;
@override
bool moveNext() {
while (_iterator.moveNext()) {
Builder candidate = _iterator.current;
if (_include(candidate)) {
return true;
}
}
return false;
}
}
/// Filtered [NameIterator].
///
/// Compared to [FilteredIterator] this iterator also gives
/// access to the name that the builders are mapped to.
class FilteredNameIterator<T extends Builder> implements NameIterator<T> {
final NameIterator<Builder> _iterator;
final Builder? parent;
final bool includeDuplicates;
final bool includeAugmentations;
FilteredNameIterator(this._iterator,
{required this.parent,
required this.includeDuplicates,
required this.includeAugmentations});
bool _include(Builder element) {
if (parent != null && element.parent != parent) return false;
if (!includeDuplicates &&
(element.isDuplicate || element.isConflictingAugmentationMember)) {
return false;
}
if (!includeAugmentations && element.isAugmenting) return false;
return element is T;
}
@override
T get current => _iterator.current as T;
@override
String get name => _iterator.name;
@override
bool moveNext() {
while (_iterator.moveNext()) {
Builder candidate = _iterator.current;
if (_include(candidate)) {
return true;
}
}
return false;
}
}
extension IteratorExtension<T extends Builder> on Iterator<T> {
void forEach(void Function(T) f) {
while (moveNext()) {
f(current);
}
}
// Coverage-ignore(suite): Not run.
List<T> toList() {
List<T> list = [];
while (moveNext()) {
list.add(current);
}
return list;
}
Iterator<T> join(Iterator<T> other) {
return new IteratorSequence<T>([this, other]);
}
}
extension NameIteratorExtension<T extends Builder> on NameIterator<T> {
void forEach(void Function(String, T) f) {
while (moveNext()) {
f(name, current);
}
}
}
abstract class MergedScope<T extends Builder> {
final T _origin;
final Scope _originScope;
Map<T, Scope> _augmentationScopes = {};
MergedScope(this._origin, this._originScope);
SourceLibraryBuilder get originLibrary;
void _addBuilderToMergedScope(
String name, Builder newBuilder, Builder? existingBuilder,
{required bool setter, required bool inPatchLibrary}) {
bool isAugmentationBuilder = inPatchLibrary
? newBuilder.hasPatchAnnotation
: newBuilder.isAugmentation;
if (existingBuilder != null) {
if (isAugmentationBuilder) {
existingBuilder.applyAugmentation(newBuilder);
} else {
newBuilder.isConflictingAugmentationMember = true;
Message message;
Message context;
if (newBuilder is SourceMemberBuilder &&
existingBuilder is SourceMemberBuilder) {
if (_origin is SourceLibraryBuilder) {
message = inPatchLibrary
? templateNonPatchLibraryMemberConflict.withArguments(name)
: templateNonAugmentationLibraryMemberConflict
.withArguments(name);
} else {
message = inPatchLibrary
? templateNonPatchClassMemberConflict.withArguments(name)
: templateNonAugmentationClassMemberConflict
.withArguments(name);
}
context = messageNonAugmentationMemberConflictCause;
} else if (newBuilder is SourceClassBuilder &&
existingBuilder is SourceClassBuilder) {
message = inPatchLibrary
? templateNonPatchClassConflict.withArguments(name)
: templateNonAugmentationClassConflict.withArguments(name);
context = messageNonAugmentationClassConflictCause;
} else {
if (_origin is SourceLibraryBuilder) {
message = inPatchLibrary
? templateNonPatchLibraryConflict.withArguments(name)
: templateNonAugmentationLibraryConflict.withArguments(name);
} else {
// Coverage-ignore-block(suite): Not run.
message = inPatchLibrary
? templateNonPatchClassMemberConflict.withArguments(name)
: templateNonAugmentationClassMemberConflict
.withArguments(name);
}
context = messageNonAugmentationMemberConflictCause;
}
originLibrary.addProblem(
message, newBuilder.charOffset, name.length, newBuilder.fileUri,
context: [
context.withLocation(existingBuilder.fileUri!,
existingBuilder.charOffset, name.length)
]);
}
} else {
if (isAugmentationBuilder) {
Message message;
if (newBuilder is SourceMemberBuilder) {
if (_origin is SourceLibraryBuilder) {
message = inPatchLibrary
? templateUnmatchedPatchLibraryMember.withArguments(name)
: templateUnmatchedAugmentationLibraryMember
.withArguments(name);
} else {
message = inPatchLibrary
? templateUnmatchedPatchClassMember.withArguments(name)
: templateUnmatchedAugmentationClassMember.withArguments(name);
}
} else if (newBuilder is SourceClassBuilder) {
message = inPatchLibrary
? templateUnmatchedPatchClass.withArguments(name)
: templateUnmatchedAugmentationClass.withArguments(name);
} else {
message = inPatchLibrary
? templateUnmatchedPatchDeclaration.withArguments(name)
:
// Coverage-ignore(suite): Not run.
templateUnmatchedAugmentationDeclaration.withArguments(name);
}
originLibrary.addProblem(
message, newBuilder.charOffset, name.length, newBuilder.fileUri);
} else {
if (inPatchLibrary &&
!name.startsWith('_') &&
!_allowInjectedPublicMember(newBuilder)) {
originLibrary.addProblem(
templatePatchInjectionFailed.withArguments(
name, originLibrary.importUri),
newBuilder.charOffset,
noLength,
newBuilder.fileUri);
}
_originScope.addLocalMember(name, newBuilder, setter: setter);
if (newBuilder is ExtensionBuilder) {
_originScope.addExtension(newBuilder);
}
for (Scope augmentationScope in _augmentationScopes.values) {
_addBuilderToAugmentationScope(augmentationScope, name, newBuilder,
setter: setter);
}
}
}
}
void _addBuilderToAugmentationScope(
Scope augmentationScope, String name, Builder member,
{required bool setter}) {
Builder? augmentationMember =
augmentationScope.lookupLocalMember(name, setter: setter);
if (augmentationMember == null) {
augmentationScope.addLocalMember(name, member, setter: setter);
if (member is ExtensionBuilder) {
augmentationScope.addExtension(member);
}
}
}
void _addAugmentationScope(T parentBuilder, Scope scope,
{required bool inPatchLibrary}) {
// TODO(johnniwinther): Use `scope.filteredNameIterator` instead of
// `scope.forEachLocalMember`/`scope.forEachLocalSetter`.
// Include all augmentation scope members to the origin scope.
scope.forEachLocalMember((String name, Builder member) {
// In case of duplicates we use the first declaration.
while (member.isDuplicate) {
member = member.next!;
}
_addBuilderToMergedScope(
name, member, _originScope.lookupLocalMember(name, setter: false),
setter: false, inPatchLibrary: inPatchLibrary);
});
Map<String, List<Builder>>? augmentations = scope.augmentations;
if (augmentations != null) {
for (String augmentedName in augmentations.keys) {
for (Builder augmentation in augmentations[augmentedName]!) {
_addBuilderToMergedScope(augmentedName, augmentation,
_originScope.lookupLocalMember(augmentedName, setter: false),
setter: false, inPatchLibrary: inPatchLibrary);
}
}
}
scope.forEachLocalSetter((String name, Builder member) {
// In case of duplicates we use the first declaration.
while (member.isDuplicate) {
member = member.next!;
}
_addBuilderToMergedScope(
name, member, _originScope.lookupLocalMember(name, setter: true),
setter: true, inPatchLibrary: inPatchLibrary);
});
Map<String, List<Builder>>? setterAugmentations = scope.setterAugmentations;
if (setterAugmentations != null) {
for (String augmentedName in setterAugmentations.keys) {
for (Builder augmentation in setterAugmentations[augmentedName]!) {
_addBuilderToMergedScope(augmentedName, augmentation,
_originScope.lookupLocalMember(augmentedName, setter: true),
setter: true, inPatchLibrary: inPatchLibrary);
}
}
}
scope.forEachLocalExtension((ExtensionBuilder extensionBuilder) {
if (extensionBuilder is SourceExtensionBuilder &&
extensionBuilder.isUnnamedExtension) {
_originScope.addExtension(extensionBuilder);
for (Scope augmentationScope in _augmentationScopes.values) {
augmentationScope.addExtension(extensionBuilder);
}
}
});
// Include all origin scope members in the augmentation scope.
_originScope.forEachLocalMember((String name, Builder originMember) {
_addBuilderToAugmentationScope(scope, name, originMember, setter: false);
});
_originScope.forEachLocalSetter((String name, Builder originMember) {
_addBuilderToAugmentationScope(scope, name, originMember, setter: true);
});
_originScope.forEachLocalExtension((ExtensionBuilder extensionBuilder) {
if (extensionBuilder is SourceExtensionBuilder &&
extensionBuilder.isUnnamedExtension) {
scope.addExtension(extensionBuilder);
}
});
_augmentationScopes[parentBuilder] = scope;
}
bool _allowInjectedPublicMember(Builder newBuilder);
}
class MergedLibraryScope extends MergedScope<SourceLibraryBuilder> {
MergedLibraryScope(SourceLibraryBuilder origin) : super(origin, origin.scope);
@override
SourceLibraryBuilder get originLibrary => _origin;
void addAugmentationScope(SourceLibraryBuilder builder) {
_addAugmentationScope(builder, builder.scope,
inPatchLibrary: builder.isPatchLibrary);
}
@override
bool _allowInjectedPublicMember(Builder newBuilder) {
return originLibrary.importUri.isScheme("dart") &&
originLibrary.importUri.path.startsWith("_");
}
}
class MergedClassMemberScope extends MergedScope<SourceClassBuilder> {
final ConstructorScope _originConstructorScope;
Map<SourceClassBuilder, ConstructorScope> _augmentationConstructorScopes = {};
MergedClassMemberScope(SourceClassBuilder origin)
: _originConstructorScope = origin.constructorScope,
super(origin, origin.scope);
@override
SourceLibraryBuilder get originLibrary => _origin.libraryBuilder;
void _addAugmentationConstructorScope(ConstructorScope constructorScope,
{required bool inPatchLibrary}) {
constructorScope._local
.forEach((String name, MemberBuilder newConstructor) {
MemberBuilder? existingConstructor =
_originConstructorScope.lookupLocalMember(name);
bool isAugmentationBuilder = inPatchLibrary
? newConstructor.hasPatchAnnotation
: newConstructor.isAugmentation;
if (existingConstructor != null) {
if (isAugmentationBuilder) {
existingConstructor.applyAugmentation(newConstructor);
} else {
newConstructor.isConflictingAugmentationMember = true;
originLibrary.addProblem(
inPatchLibrary
? templateNonPatchConstructorConflict
.withArguments(newConstructor.fullNameForErrors)
:
// Coverage-ignore(suite): Not run.
templateNonAugmentationConstructorConflict
.withArguments(newConstructor.fullNameForErrors),
newConstructor.charOffset,
noLength,
newConstructor.fileUri,
context: [
messageNonAugmentationConstructorConflictCause.withLocation(
existingConstructor.fileUri!,
existingConstructor.charOffset,
noLength)
]);
}
} else {
if (isAugmentationBuilder) {
originLibrary.addProblem(
inPatchLibrary
? templateUnmatchedPatchConstructor
.withArguments(newConstructor.fullNameForErrors)
:
// Coverage-ignore(suite): Not run.
templateUnmatchedAugmentationConstructor
.withArguments(newConstructor.fullNameForErrors),
newConstructor.charOffset,
noLength,
newConstructor.fileUri);
} else {
_originConstructorScope.addLocalMember(name, newConstructor);
for (ConstructorScope augmentationConstructorScope
in _augmentationConstructorScopes.values) {
// Coverage-ignore-block(suite): Not run.
_addConstructorToAugmentationScope(
augmentationConstructorScope, name, newConstructor);
}
}
if (inPatchLibrary &&
!name.startsWith('_') &&
!_allowInjectedPublicMember(newConstructor)) {
// Coverage-ignore-block(suite): Not run.
originLibrary.addProblem(
templatePatchInjectionFailed.withArguments(
name, originLibrary.importUri),
newConstructor.charOffset,
noLength,
newConstructor.fileUri);
}
}
});
_originConstructorScope._local
.forEach((String name, MemberBuilder originConstructor) {
_addConstructorToAugmentationScope(
constructorScope, name, originConstructor);
});
}
void _addConstructorToAugmentationScope(
ConstructorScope augmentationConstructorScope,
String name,
MemberBuilder constructor) {
Builder? augmentationConstructor =
augmentationConstructorScope.lookupLocalMember(name);
if (augmentationConstructor == null) {
augmentationConstructorScope.addLocalMember(name, constructor);
}
}
// TODO(johnniwinther): Check for conflicts between constructors and class
// members.
void addAugmentationScope(SourceClassBuilder builder) {
_addAugmentationScope(builder, builder.scope,
inPatchLibrary: builder.libraryBuilder.isPatchLibrary);
_addAugmentationConstructorScope(builder.constructorScope,
inPatchLibrary: builder.libraryBuilder.isPatchLibrary);
}
@override
bool _allowInjectedPublicMember(Builder newBuilder) {
if (originLibrary.importUri.isScheme("dart") &&
originLibrary.importUri.path.startsWith("_")) {
return true;
}
if (newBuilder.isStatic) {
// Coverage-ignore-block(suite): Not run.
return _origin.name.startsWith('_');
}
// TODO(johnniwinther): Restrict the use of injected public class members.
return true;
}
}
extension on Builder {
bool get isAugmentation {
Builder self = this;
if (self is SourceLibraryBuilder) {
// Coverage-ignore-block(suite): Not run.
return self.isAugmentationLibrary;
} else if (self is SourceClassBuilder) {
return self.isAugmentation;
} else if (self is SourceMemberBuilder) {
return self.isAugmentation;
} else {
// TODO(johnniwinther): Handle all cases here.
return false;
}
}
bool get isConflictingAugmentationMember {
Builder self = this;
if (self is SourceMemberBuilder) {
return self.isConflictingAugmentationMember;
} else if (self is SourceClassBuilder) {
return self.isConflictingAugmentationMember;
}
// TODO(johnniwinther): Handle all cases here.
return false;
}
void set isConflictingAugmentationMember(bool value) {
Builder self = this;
if (self is SourceMemberBuilder) {
self.isConflictingAugmentationMember = value;
} else if (self is SourceClassBuilder) {
self.isConflictingAugmentationMember = value;
}
// TODO(johnniwinther): Handle all cases here.
}
bool _hasPatchAnnotation(List<MetadataBuilder>? metadata) {
if (metadata == null) {
return false;
}
for (MetadataBuilder metadataBuilder in metadata) {
if (metadataBuilder.hasPatch) {
return true;
}
}
return false;
}
bool get hasPatchAnnotation {
Builder self = this;
if (self is SourceFunctionBuilder) {
return _hasPatchAnnotation(self.metadata);
} else if (self is SourceClassBuilder) {
return _hasPatchAnnotation(self.metadata);
} else if (self is SourceExtensionBuilder) {
return _hasPatchAnnotation(self.metadata);
} else if (self is SourceExtensionTypeDeclarationBuilder) {
// Coverage-ignore-block(suite): Not run.
return _hasPatchAnnotation(self.metadata);
}
return false;
}
}
class IteratorSequence<T> implements Iterator<T> {
Iterator<Iterator<T>> _iterators;
Iterator<T>? _current;
IteratorSequence(Iterable<Iterator<T>> iterators)
: _iterators = iterators.iterator;
@override
T get current {
if (_current != null) {
return _current!.current;
}
// Coverage-ignore-block(suite): Not run.
throw new StateError("No current element");
}
@override
bool moveNext() {
if (_current != null) {
if (_current!.moveNext()) {
return true;
}
_current = null;
}
while (_iterators.moveNext()) {
_current = _iterators.current;
if (_current!.moveNext()) {
return true;
}
_current = null;
}
return false;
}
}