blob: 498b0740595b4ba699d05ea69b92bd7b71f3530f [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.source_library_builder;
import 'package:kernel/ast.dart' show AsyncMarker, ProcedureKind;
import '../combinator.dart' show Combinator;
import '../errors.dart' show internalError;
import '../messages.dart' show warning;
import '../import.dart' show Import;
import 'source_loader.dart' show SourceLoader;
import '../builder/scope.dart' show Scope;
import '../builder/builder.dart'
show
Builder,
ClassBuilder,
ConstructorReferenceBuilder,
FormalParameterBuilder,
LibraryBuilder,
MemberBuilder,
MetadataBuilder,
PrefixBuilder,
ProcedureBuilder,
TypeBuilder,
TypeDeclarationBuilder,
TypeVariableBuilder,
Unhandled;
abstract class SourceLibraryBuilder<T extends TypeBuilder, R>
extends LibraryBuilder<T, R> {
final SourceLoader loader;
final DeclarationBuilder<T> libraryDeclaration =
new DeclarationBuilder<T>(<String, Builder>{}, null);
final List<ConstructorReferenceBuilder> constructorReferences =
<ConstructorReferenceBuilder>[];
final List<SourceLibraryBuilder<T, R>> parts = <SourceLibraryBuilder<T, R>>[];
final List<Import> imports = <Import>[];
final Map<String, Builder> exports = <String, Builder>{};
final Scope scope = new Scope(<String, Builder>{}, null, isModifiable: false);
final Uri fileUri;
final List<List> implementationBuilders = <List<List>>[];
String name;
String partOf;
List<MetadataBuilder> metadata;
/// The current declaration that is being built. When we start parsing a
/// declaration (class, method, and so on), we don't have enough information
/// to create a builder and this object records its members and types until,
/// for example, [addClass] is called.
DeclarationBuilder<T> currentDeclaration;
SourceLibraryBuilder(this.loader, Uri fileUri)
: fileUri = fileUri,
super(fileUri) {
currentDeclaration = libraryDeclaration;
}
Uri get uri;
bool get isPart => partOf != null;
Map<String, Builder> get members => libraryDeclaration.members;
List<T> get types => libraryDeclaration.types;
/// When parsing a class, this returns a map of its members (that have been
/// parsed so far).
Map<String, MemberBuilder> get classMembers {
assert(currentDeclaration.parent == libraryDeclaration);
return currentDeclaration.members;
}
T addNamedType(String name, List<T> arguments, int charOffset);
T addMixinApplication(T supertype, List<T> mixins, int charOffset);
T addType(T type) {
currentDeclaration.addType(type);
return type;
}
T addVoidType(int charOffset);
ConstructorReferenceBuilder addConstructorReference(
String name, List<T> typeArguments, String suffix, int charOffset) {
ConstructorReferenceBuilder ref = new ConstructorReferenceBuilder(
name, typeArguments, suffix, this, charOffset);
constructorReferences.add(ref);
return ref;
}
void beginNestedDeclaration(String name, {bool hasMembers}) {
currentDeclaration = new DeclarationBuilder(
<String, MemberBuilder>{}, name, currentDeclaration);
}
DeclarationBuilder<T> endNestedDeclaration() {
DeclarationBuilder<T> previous = currentDeclaration;
currentDeclaration = currentDeclaration.parent;
return previous;
}
Uri resolve(String path) => uri.resolve(path);
void addExport(List<MetadataBuilder> metadata, String uri,
Unhandled conditionalUris, List<Combinator> combinators, int charOffset) {
loader.read(resolve(uri)).addExporter(this, combinators, charOffset);
}
void addImport(
List<MetadataBuilder> metadata,
String uri,
Unhandled conditionalUris,
String prefix,
List<Combinator> combinators,
bool deferred,
int charOffset,
int prefixCharOffset) {
imports.add(new Import(this, loader.read(resolve(uri)), prefix, combinators,
charOffset, prefixCharOffset));
}
void addPart(List<MetadataBuilder> metadata, String path) {
Uri resolvedUri;
Uri newFileUri;
if (uri.scheme == "dart") {
resolvedUri = new Uri(scheme: "dart", path: "${uri.path}/$path");
newFileUri = fileUri.resolve(path);
} else {
resolvedUri = uri.resolve(path);
newFileUri = fileUri.resolve(path);
}
parts.add(loader.read(resolvedUri, newFileUri));
}
void addPartOf(List<MetadataBuilder> metadata, String name) {
partOf = name;
}
void addClass(
List<MetadataBuilder> metadata,
int modifiers,
String name,
List<TypeVariableBuilder> typeVariables,
T supertype,
List<T> interfaces,
int charOffset);
void addNamedMixinApplication(
List<MetadataBuilder> metadata,
String name,
List<TypeVariableBuilder> typeVariables,
int modifiers,
T mixinApplication,
List<T> interfaces,
int charOffset);
void addField(List<MetadataBuilder> metadata, int modifiers, T type,
String name, int charOffset);
void addFields(List<MetadataBuilder> metadata, int modifiers, T type,
List<String> names) {
for (String name in names) {
// TODO(ahe): Get charOffset of name.
addField(metadata, modifiers, type, name, -1);
}
}
void addProcedure(
List<MetadataBuilder> metadata,
int modifiers,
T returnType,
String name,
List<TypeVariableBuilder> typeVariables,
List<FormalParameterBuilder> formals,
AsyncMarker asyncModifier,
ProcedureKind kind,
int charOffset,
String nativeMethodName,
{bool isTopLevel});
void addEnum(List<MetadataBuilder> metadata, String name,
List<String> constants, int charOffset);
void addFunctionTypeAlias(
List<MetadataBuilder> metadata,
T returnType,
String name,
List<TypeVariableBuilder> typeVariables,
List<FormalParameterBuilder> formals,
int charOffset);
void addFactoryMethod(
List<MetadataBuilder> metadata,
int modifiers,
ConstructorReferenceBuilder name,
List<FormalParameterBuilder> formals,
AsyncMarker asyncModifier,
ConstructorReferenceBuilder redirectionTarget,
int charOffset,
String nativeMethodName);
FormalParameterBuilder addFormalParameter(List<MetadataBuilder> metadata,
int modifiers, T type, String name, bool hasThis, int charOffset);
TypeVariableBuilder addTypeVariable(String name, T bound, int charOffset);
Builder addBuilder(String name, Builder builder, int charOffset) {
if (name.indexOf(".") != -1 && name.indexOf("&") == -1) {
addCompileTimeError(
charOffset,
"Only constructors and factories can have"
" names containing a period ('.'): $name");
}
// TODO(ahe): Set the parent correctly here. Could then change the
// implementation of MemberBuilder.isTopLevel to test explicitly for a
// LibraryBuilder.
if (currentDeclaration == libraryDeclaration) {
if (builder is MemberBuilder) {
builder.parent = this;
} else if (builder is TypeDeclarationBuilder) {
builder.parent = this;
} else if (builder is PrefixBuilder) {
assert(builder.parent == this);
} else {
return internalError("Unhandled: ${builder.runtimeType}");
}
} else {
assert(currentDeclaration.parent == libraryDeclaration);
}
Map<String, Builder> members = currentDeclaration.members;
Builder existing = members[name];
builder.next = existing;
if (builder is PrefixBuilder && existing is PrefixBuilder) {
assert(existing.next == null);
builder.exports.forEach((String name, Builder builder) {
Builder other = existing.exports.putIfAbsent(name, () => builder);
if (other != builder) {
existing.exports[name] =
other.combineAmbiguousImport(name, builder, this);
}
});
return existing;
} else if (isDuplicatedDefinition(existing, builder)) {
addCompileTimeError(charOffset, "Duplicated definition of '$name'.");
}
return members[name] = builder;
}
bool isDuplicatedDefinition(Builder existing, Builder other) {
if (existing == null) return false;
Builder next = existing.next;
if (next == null) {
if (existing.isGetter && other.isSetter) return false;
if (existing.isSetter && other.isGetter) return false;
} else {
if (next is ClassBuilder && !next.isMixinApplication) return true;
}
if (existing is ClassBuilder && other is ClassBuilder) {
// We allow multiple mixin applications with the same name. An
// alternative is to share these mixin applications. This situation can
// happen if you have `class A extends Object with Mixin {}` and `class B
// extends Object with Mixin {}` in the same library.
return !existing.isMixinApplication || !other.isMixinApplication;
}
return true;
}
void buildBuilder(Builder builder);
R build() {
assert(implementationBuilders.isEmpty);
members.forEach((String name, Builder builder) {
do {
buildBuilder(builder);
builder = builder.next;
} while (builder != null);
});
for (List list in implementationBuilders) {
String name = list[0];
Builder builder = list[1];
int charOffset = list[2];
addBuilder(name, builder, charOffset);
buildBuilder(builder);
}
return null;
}
void addImplementationBuilder(String name, Builder builder, int charOffset) {
implementationBuilders.add([name, builder, charOffset]);
}
void validatePart() {
if (parts.isNotEmpty) {
internalError("Part with parts: $uri");
}
if (exporters.isNotEmpty) {
internalError(
"${exporters.first.exporter.uri} attempts to export the part $uri.");
}
}
void includeParts() {
for (SourceLibraryBuilder<T, R> part in parts.toList()) {
includePart(part);
}
}
void includePart(SourceLibraryBuilder<T, R> part) {
if (name != null) {
if (part.partOf == null) {
warning(
part.fileUri,
-1,
"Has no 'part of' declaration but is used as "
"a part by ${name} ($uri).");
parts.remove(part);
return;
}
if (part.partOf != name) {
warning(
part.fileUri,
-1,
"Is part of '${part.partOf}' but is used as "
"a part by '${name}' ($uri).");
parts.remove(part);
return;
}
}
part.members.forEach((String name, Builder builder) {
if (builder.next != null) {
assert(builder.next.next == null);
addBuilder(name, builder.next, builder.next.charOffset);
}
addBuilder(name, builder, builder.charOffset);
});
types.addAll(part.types);
constructorReferences.addAll(part.constructorReferences);
part.partOfLibrary = this;
// TODO(ahe): Include metadata from part?
}
void buildInitialScopes() {
members.forEach(addToExportScope);
members.forEach(addToScope);
}
void addImportsToScope() {
bool explicitCoreImport = this == loader.coreLibrary;
for (Import import in imports) {
if (import.imported == loader.coreLibrary) {
explicitCoreImport = true;
}
import.finalizeImports(this);
}
if (!explicitCoreImport) {
loader.coreLibrary.exports.forEach(addToScope);
}
}
void addToScope(String name, Builder member) {
Builder existing = scope.lookup(name, member.charOffset, fileUri);
if (existing != null) {
if (existing != member) {
scope.local[name] = existing.combineAmbiguousImport(name, member, this);
}
// TODO(ahe): handle duplicated names.
} else {
scope.local[name] = member;
}
}
bool addToExportScope(String name, Builder member) {
if (name.startsWith("_")) return false;
if (member is PrefixBuilder) return false;
Builder existing = exports[name];
if (existing != null) {
// TODO(ahe): handle duplicated names.
return false;
} else {
exports[name] = member;
}
return true;
}
int resolveTypes(_) {
int typeCount = types.length;
for (T t in types) {
t.resolveIn(scope);
}
members.forEach((String name, Builder member) {
typeCount += member.resolveTypes(this);
});
return typeCount;
}
int resolveConstructors(_) {
int count = 0;
members.forEach((String name, Builder member) {
count += member.resolveConstructors(this);
});
return count;
}
List<TypeVariableBuilder> copyTypeVariables(
List<TypeVariableBuilder> original);
}
/// Unlike [Scope], this scope is used during construction of builders to
/// ensure types and members are added to and resolved in the correct location.
class DeclarationBuilder<T extends TypeBuilder> {
final DeclarationBuilder<T> parent;
final Map<String, Builder> members;
final List<T> types = <T>[];
final String name;
final Map<ProcedureBuilder, DeclarationBuilder<T>> factoryDeclarations =
<ProcedureBuilder, DeclarationBuilder<T>>{};
DeclarationBuilder(this.members, this.name, [this.parent]);
void addMember(String name, MemberBuilder builder) {
if (members == null) {
parent.addMember(name, builder);
} else {
members[name] = builder;
}
}
MemberBuilder lookupMember(String name) {
return members == null ? parent.lookupMember(name) : members[name];
}
void addType(T type) {
types.add(type);
}
/// Resolves type variables in [types] and propagate other types to [parent].
void resolveTypes(
List<TypeVariableBuilder> typeVariables, SourceLibraryBuilder library) {
// TODO(ahe): The input to this method, [typeVariables], shouldn't be just
// type variables. It should be everything that's in scope, for example,
// members (of a class) or formal parameters (of a method).
if (typeVariables == null) {
// If there are no type variables in the scope, propagate our types to be
// resolved in the parent declaration.
factoryDeclarations.forEach((_, DeclarationBuilder<T> declaration) {
parent.types.addAll(declaration.types);
});
parent.types.addAll(types);
} else {
factoryDeclarations.forEach(
(ProcedureBuilder procedure, DeclarationBuilder<T> declaration) {
assert(procedure.typeVariables.isEmpty);
procedure.typeVariables
.addAll(library.copyTypeVariables(typeVariables));
declaration.resolveTypes(procedure.typeVariables, library);
});
Map<String, TypeVariableBuilder> map = <String, TypeVariableBuilder>{};
for (TypeVariableBuilder builder in typeVariables) {
map[builder.name] = builder;
}
for (T type in types) {
String name = type.name;
TypeVariableBuilder builder;
if (name != null) {
builder = map[name];
}
if (builder == null) {
// Since name didn't resolve in this scope, propagate it to the
// parent declaration.
parent.addType(type);
} else {
type.bind(builder);
}
}
}
types.clear();
}
/// Called to register [procedure] as a factory whose types are collected in
/// [factoryDeclaration]. Later, once the class has been built, we can
/// synthesize type variables on the factory matching the class'.
void addFactoryDeclaration(
ProcedureBuilder procedure, DeclarationBuilder<T> factoryDeclaration) {
factoryDeclarations[procedure] = factoryDeclaration;
}
}