blob: 1e6aa923981142dd7f414ac9f0e480ac748b7048 [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.
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/class_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';
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 'problems.dart' show internalProblem, unsupported;
import 'source/source_class_builder.dart';
import 'source/source_extension_builder.dart';
import 'source/source_library_builder.dart';
import 'source/source_member_builder.dart';
import 'util/helpers.dart' show DelayedActionPerformer;
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,
/// 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,
}
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;
final ScopeKind kind;
MutableScope(this.kind, 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(${kind}, $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 ScopeKind kind,
Map<String, Builder>? local,
Map<String, MemberBuilder>? setters,
Set<ExtensionBuilder>? extensions,
Scope? parent,
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.immutable({required ScopeKind kind})
: this(
kind: kind,
local: const <String, Builder>{},
setters: const <String, MemberBuilder>{},
debugName: "immutable",
isModifiable: false);
Scope.nested(Scope parent, String debugName,
{bool isModifiable = true, required ScopeKind kind})
: this(
kind: kind,
parent: parent,
debugName: debugName,
isModifiable: isModifiable);
/// 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) {
// 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(
kind: kind,
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(
{required String debugName,
bool isModifiable = true,
required ScopeKind kind}) {
return new Scope.nested(this, debugName,
isModifiable: isModifiable, kind: kind);
}
Scope withTypeVariables(List<TypeVariableBuilder>? typeVariables) {
if (typeVariables == null) return this;
Scope newScope = new Scope.nested(this, "type variables",
isModifiable: false, kind: ScopeKind.typeParameters);
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() {
// The scopes needs to reference the same locals and setters so we have to
// eagerly initialize them.
_local ??= {};
_setters ??= {};
return new Scope(
kind: ScopeKind.labels,
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;
}
}
/// Lookup a member with [name] in the scope.
Builder? lookup(String name, int charOffset, Uri fileUri,
{bool isInstanceScope = true}) {
recordUse(name, charOffset);
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);
}
Builder? lookupSetter(String name, int charOffset, Uri fileUri,
{bool isInstanceScope = true}) {
recordUse(name, charOffset);
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 ??= {})[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);
}
ExtensionBuilder? lookupLocalUnnamedExtension(Uri fileUri, int offset) {
if (_extensions != null) {
for (ExtensionBuilder extension in _extensions!) {
if (extension.fileUri == fileUri && extension.charOffset == offset) {
return extension;
}
}
}
return null;
}
void forEachLocalExtension(void Function(ExtensionBuilder member) f) {
_extensions?.forEach(f);
}
Iterable<Builder> get localMembers => _local?.values ?? const {};
Iterable<MemberBuilder> get localSetters => _setters?.values ?? const {};
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;
}
JumpTarget? 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 = 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 ??= {};
scope._local?.forEach(mergeMember);
}
if (scope._setters != null) {
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;
}
}
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;
}
}
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
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 isInlineClassInstanceMember => builder.isInlineClassInstanceMember;
@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
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
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
ProcedureKind? get kind => null;
@override
void buildOutlineExpressions(
ClassHierarchy classHierarchy,
List<DelayedActionPerformer> delayedActionPerformers,
List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
throw new UnsupportedError('$runtimeType.buildOutlineExpressions');
}
@override
void buildOutlineNodes(void Function(Member, BuiltMemberKind) f) {
assert(false, "Unexpected call to $runtimeType.buildOutlineNodes.");
}
@override
int buildBodyNodes(void Function(Member, BuiltMemberKind) f) {
assert(false, "Unexpected call to $runtimeType.buildBodyNodes.");
return 0;
}
@override
List<ClassMember> get localMembers => const <ClassMember>[];
@override
List<ClassMember> get localSetters => const <ClassMember>[];
@override
void checkVariance(
SourceClassBuilder sourceClassBuilder, TypeEnvironment typeEnvironment) {
assert(false, "Unexpected call to $runtimeType.checkVariance.");
}
@override
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 get bodyBuilderContext {
throw new UnsupportedError(
'$runtimeType.bodyBuilderContextForAnnotations}');
}
}
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(Scope scope)
: local = scope._local?.values.iterator,
setters = scope._setters?.values.iterator,
extensions = 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 ?? (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 ?? (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 ?? (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 ?? (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.isPatch) 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.isPatch) 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);
}
}
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(T parentBuilder, String name,
Builder newBuilder, Builder? existingBuilder,
{required bool setter}) {
if (existingBuilder != null) {
if (parentBuilder.isAugmentation) {
if (newBuilder.isAugmentation) {
existingBuilder.applyPatch(newBuilder);
} else {
newBuilder.isConflictingAugmentationMember = true;
Message message;
Message context;
if (newBuilder is SourceMemberBuilder &&
existingBuilder is SourceMemberBuilder) {
if (_origin is SourceLibraryBuilder) {
message = templateNonAugmentationLibraryMemberConflict
.withArguments(name);
} else {
message = templateNonAugmentationClassMemberConflict
.withArguments(name);
}
context = messageNonAugmentationMemberConflictCause;
} else if (newBuilder is SourceClassBuilder &&
existingBuilder is SourceClassBuilder) {
message = templateNonAugmentationClassConflict.withArguments(name);
context = messageNonAugmentationClassConflictCause;
} else {
if (_origin is SourceLibraryBuilder) {
message =
templateNonAugmentationLibraryConflict.withArguments(name);
} else {
message = templateNonAugmentationClassMemberConflict
.withArguments(name);
}
context = messageNonAugmentationMemberConflictCause;
}
originLibrary.addProblem(
message, newBuilder.charOffset, name.length, newBuilder.fileUri,
context: [
context.withLocation(existingBuilder.fileUri!,
existingBuilder.charOffset, name.length)
]);
}
} else {
// Patch libraries implicitly assume matching members are patch
// members.
existingBuilder.applyPatch(newBuilder);
}
} else {
if (newBuilder.isAugmentation) {
Message message;
if (newBuilder is SourceMemberBuilder) {
if (_origin is SourceLibraryBuilder) {
message =
templateUnmatchedAugmentationLibraryMember.withArguments(name);
} else {
message =
templateUnmatchedAugmentationClassMember.withArguments(name);
}
} else if (newBuilder is SourceClassBuilder) {
message = templateUnmatchedAugmentationClass.withArguments(name);
} else {
message =
templateUnmatchedAugmentationDeclaration.withArguments(name);
}
originLibrary.addProblem(
message, newBuilder.charOffset, name.length, newBuilder.fileUri);
} else {
if (!parentBuilder.isAugmentation && !name.startsWith('_')) {
// We special-case public members injected in patch libraries.
// TODO(johnniwinther): Avoid this special-casing and just report the
// error.
_addInjectedPatchMember(name, newBuilder);
} else {
_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) {
// 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(parentBuilder, name, member,
_originScope.lookupLocalMember(name, setter: false),
setter: false);
});
scope.forEachLocalSetter((String name, Builder member) {
// In case of duplicates we use the first declaration.
while (member.isDuplicate) {
member = member.next!;
}
_addBuilderToMergedScope(parentBuilder, name, member,
_originScope.lookupLocalMember(name, setter: true),
setter: true);
});
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;
}
void _addInjectedPatchMember(String name, 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);
}
@override
void _addInjectedPatchMember(String name, Builder newBuilder) {
assert(!name.startsWith('_'), "Unexpected private member $newBuilder");
_exportMemberFromPatch(name, newBuilder);
}
void _exportMemberFromPatch(String name, Builder member) {
if (!originLibrary.importUri.isScheme("dart") ||
!originLibrary.importUri.path.startsWith("_")) {
originLibrary.addProblem(
templatePatchInjectionFailed.withArguments(
name, originLibrary.importUri),
member.charOffset,
noLength,
member.fileUri);
}
// Platform-private libraries, such as "dart:_internal" have special
// semantics: public members are injected into the origin library.
// TODO(ahe): See if we can remove this special case.
// If this member already exist in the origin library scope, it should
// have been marked as patch.
assert((member.isSetter &&
_originScope.lookupLocalMember(name, setter: true) == null) ||
(!member.isSetter &&
_originScope.lookupLocalMember(name, setter: false) == null));
originLibrary.addToExportScope(name, member);
}
}
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(
SourceClassBuilder classBuilder, ConstructorScope constructorScope) {
constructorScope._local
.forEach((String name, MemberBuilder newConstructor) {
MemberBuilder? existingConstructor =
_originConstructorScope.lookupLocalMember(name);
if (classBuilder.isAugmentation) {
if (existingConstructor != null) {
if (newConstructor.isAugmentation) {
existingConstructor.applyPatch(newConstructor);
} else {
newConstructor.isConflictingAugmentationMember = true;
originLibrary.addProblem(
templateNonAugmentationConstructorConflict
.withArguments(newConstructor.fullNameForErrors),
newConstructor.charOffset,
noLength,
newConstructor.fileUri,
context: [
messageNonAugmentationConstructorConflictCause.withLocation(
existingConstructor.fileUri!,
existingConstructor.charOffset,
noLength)
]);
}
} else {
if (newConstructor.isAugmentation) {
originLibrary.addProblem(
templateUnmatchedAugmentationConstructor
.withArguments(newConstructor.fullNameForErrors),
newConstructor.charOffset,
noLength,
newConstructor.fileUri);
} else {
_originConstructorScope.addLocalMember(name, newConstructor);
for (ConstructorScope augmentationConstructorScope
in _augmentationConstructorScopes.values) {
_addConstructorToAugmentationScope(
augmentationConstructorScope, name, newConstructor);
}
}
}
} else {
if (existingConstructor != null) {
// Patch libraries implicitly assume matching members are patch
// members.
existingConstructor.applyPatch(newConstructor);
} else if (name.startsWith('_')) {
// Members injected into patch are not part of the origin scope.
_originConstructorScope.addLocalMember(name, newConstructor);
for (ConstructorScope augmentationScope
in _augmentationConstructorScopes.values) {
_addConstructorToAugmentationScope(
augmentationScope, name, newConstructor);
}
}
}
});
_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);
_addAugmentationConstructorScope(builder, builder.constructorScope);
}
@override
void _addInjectedPatchMember(String name, Builder newBuilder) {
// Members injected into patch are not part of the origin scope.
}
}
extension on Builder {
bool get isAugmentation {
Builder self = this;
if (self is SourceLibraryBuilder) {
return self.isAugmentation;
} else if (self is SourceClassBuilder) {
return self.isAugmentation;
} else if (self is SourceMemberBuilder) {
return self.isAugmentation;
} else {
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.
}
}
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;
}
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;
}
}