blob: 3adef896afd40268da2631c75a3bcc0a7aad3550 [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_class_builder;
import 'package:kernel/ast.dart'
show Class, Constructor, Member, Supertype, TreeNode;
import '../dill/dill_member_builder.dart' show DillMemberBuilder;
import '../fasta_codes.dart'
show
Message,
noLength,
templateBadTypeVariableInSupertype,
templateConflictsWithConstructor,
templateConflictsWithFactory,
templateConflictsWithMember,
templateConflictsWithMemberWarning,
templateConflictsWithSetter,
templateConflictsWithSetterWarning,
templateSupertypeIsIllegal;
import '../kernel/kernel_builder.dart'
show
ClassBuilder,
ConstructorReferenceBuilder,
Declaration,
KernelClassBuilder,
KernelFieldBuilder,
KernelFunctionBuilder,
KernelInvalidTypeBuilder,
KernelLibraryBuilder,
KernelNamedTypeBuilder,
KernelTypeBuilder,
KernelTypeVariableBuilder,
LibraryBuilder,
MetadataBuilder,
Scope,
TypeVariableBuilder,
compareProcedures;
import '../kernel/type_algorithms.dart' show Variance, computeVariance;
import '../problems.dart' show unexpected, unhandled;
Class initializeClass(
Class cls,
List<TypeVariableBuilder> typeVariables,
String name,
KernelLibraryBuilder parent,
int startCharOffset,
int charOffset,
int charEndOffset) {
cls ??= new Class(
name: name,
typeParameters:
KernelTypeVariableBuilder.kernelTypeParametersFromBuilders(
typeVariables));
cls.fileUri ??= parent.fileUri;
if (cls.startFileOffset == TreeNode.noOffset) {
cls.startFileOffset = startCharOffset;
}
if (cls.fileOffset == TreeNode.noOffset) {
cls.fileOffset = charOffset;
}
if (cls.fileEndOffset == TreeNode.noOffset) {
cls.fileEndOffset = charEndOffset;
}
return cls;
}
class SourceClassBuilder extends KernelClassBuilder
implements Comparable<SourceClassBuilder> {
@override
final Class actualCls;
final List<ConstructorReferenceBuilder> constructorReferences;
KernelTypeBuilder mixedInType;
bool isMixinDeclaration;
SourceClassBuilder(
List<MetadataBuilder> metadata,
int modifiers,
String name,
List<TypeVariableBuilder> typeVariables,
KernelTypeBuilder supertype,
List<KernelTypeBuilder> interfaces,
Scope scope,
Scope constructors,
LibraryBuilder parent,
this.constructorReferences,
int startCharOffset,
int charOffset,
int charEndOffset,
{Class cls,
this.mixedInType,
this.isMixinDeclaration = false})
: actualCls = initializeClass(cls, typeVariables, name, parent,
startCharOffset, charOffset, charEndOffset),
super(metadata, modifiers, name, typeVariables, supertype, interfaces,
scope, constructors, parent, charOffset);
@override
Class get cls => origin.actualCls;
@override
KernelLibraryBuilder get library => super.library;
Class build(KernelLibraryBuilder library, LibraryBuilder coreLibrary) {
void buildBuilders(String name, Declaration declaration) {
do {
if (declaration.parent != this) {
if (fileUri != declaration.parent.fileUri) {
unexpected("$fileUri", "${declaration.parent.fileUri}", charOffset,
fileUri);
} else {
unexpected(fullNameForErrors, declaration.parent?.fullNameForErrors,
charOffset, fileUri);
}
} else if (declaration is KernelFieldBuilder) {
// TODO(ahe): It would be nice to have a common interface for the
// build method to avoid duplicating these two cases.
Member field = declaration.build(library);
if (!declaration.isPatch && declaration.next == null) {
cls.addMember(field);
}
} else if (declaration is KernelFunctionBuilder) {
Member function = declaration.build(library);
function.parent = cls;
if (!declaration.isPatch && declaration.next == null) {
cls.addMember(function);
}
} else {
unhandled("${declaration.runtimeType}", "buildBuilders",
declaration.charOffset, declaration.fileUri);
}
declaration = declaration.next;
} while (declaration != null);
}
scope.forEach(buildBuilders);
constructors.forEach(buildBuilders);
supertype = checkSupertype(supertype);
actualCls.supertype =
supertype?.buildSupertype(library, charOffset, fileUri);
if (!isMixinDeclaration &&
actualCls.supertype != null &&
actualCls.superclass.isMixinDeclaration) {
// Declared mixins have interfaces that can be implemented, but they
// cannot be extended. However, a mixin declaration with a single
// superclass constraint is encoded with the constraint as the supertype,
// and that is allowed to be a mixin's interface.
library.addProblem(
templateSupertypeIsIllegal.withArguments(actualCls.superclass.name),
charOffset,
noLength,
fileUri);
actualCls.supertype = null;
}
if (actualCls.supertype == null && supertype is! KernelNamedTypeBuilder) {
supertype = null;
}
mixedInType = checkSupertype(mixedInType);
actualCls.mixedInType =
mixedInType?.buildMixedInType(library, charOffset, fileUri);
if (actualCls.mixedInType == null &&
mixedInType is! KernelNamedTypeBuilder) {
mixedInType = null;
}
actualCls.isMixinDeclaration = isMixinDeclaration;
// TODO(ahe): If `cls.supertype` is null, and this isn't Object, report a
// compile-time error.
cls.isAbstract = isAbstract;
if (interfaces != null) {
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = checkSupertype(interfaces[i]);
Supertype supertype =
interfaces[i].buildSupertype(library, charOffset, fileUri);
if (supertype != null) {
// TODO(ahe): Report an error if supertype is null.
actualCls.implementedTypes.add(supertype);
}
}
}
constructors.forEach((String name, Declaration constructor) {
Declaration member = scopeBuilder[name];
if (member == null) return;
if (!member.isStatic) return;
// TODO(ahe): Revisit these messages. It seems like the last two should
// be `context` parameter to this message.
addProblem(templateConflictsWithMember.withArguments(name),
constructor.charOffset, noLength);
if (constructor.isFactory) {
addProblem(
templateConflictsWithFactory.withArguments("${this.name}.${name}"),
member.charOffset,
noLength);
} else {
addProblem(
templateConflictsWithConstructor
.withArguments("${this.name}.${name}"),
member.charOffset,
noLength);
}
});
scope.setters.forEach((String name, Declaration setter) {
Declaration member = scopeBuilder[name];
if (member == null ||
!(member.isField && !member.isFinal && !member.isConst ||
member.isRegularMethod && member.isStatic && setter.isStatic)) {
return;
}
if (member.isInstanceMember == setter.isInstanceMember) {
addProblem(templateConflictsWithMember.withArguments(name),
setter.charOffset, noLength);
// TODO(ahe): Context argument to previous message?
addProblem(templateConflictsWithSetter.withArguments(name),
member.charOffset, noLength);
} else {
addProblem(templateConflictsWithMemberWarning.withArguments(name),
setter.charOffset, noLength);
// TODO(ahe): Context argument to previous message?
addProblem(templateConflictsWithSetterWarning.withArguments(name),
member.charOffset, noLength);
}
});
scope.setters.forEach((String name, Declaration setter) {
Declaration constructor = constructorScopeBuilder[name];
if (constructor == null || !setter.isStatic) return;
addProblem(templateConflictsWithConstructor.withArguments(name),
setter.charOffset, noLength);
addProblem(templateConflictsWithSetter.withArguments(name),
constructor.charOffset, noLength);
});
cls.procedures.sort(compareProcedures);
return cls;
}
KernelTypeBuilder checkSupertype(KernelTypeBuilder supertype) {
if (typeVariables == null || supertype == null) return supertype;
Message message;
for (int i = 0; i < typeVariables.length; ++i) {
int variance = computeVariance(typeVariables[i], supertype);
if (variance == Variance.contravariant ||
variance == Variance.invariant) {
message = templateBadTypeVariableInSupertype.withArguments(
typeVariables[i].name, supertype.name);
library.addProblem(message, charOffset, noLength, fileUri);
}
}
if (message != null) {
return new KernelNamedTypeBuilder(supertype.name, null)
..bind(new KernelInvalidTypeBuilder(supertype.name,
message.withLocation(fileUri, charOffset, noLength)));
}
return supertype;
}
void addSyntheticConstructor(Constructor constructor) {
String name = constructor.name.name;
cls.constructors.add(constructor);
constructor.parent = cls;
DillMemberBuilder memberBuilder = new DillMemberBuilder(constructor, this);
memberBuilder.next = constructorScopeBuilder[name];
constructorScopeBuilder.addMember(name, memberBuilder);
}
@override
int finishPatch() {
if (!isPatch) return 0;
// TODO(ahe): restore file-offset once we track both origin and patch file
// URIs. See https://github.com/dart-lang/sdk/issues/31579
cls.annotations.forEach((m) => m.fileOffset = origin.cls.fileOffset);
int count = 0;
scope.forEach((String name, Declaration declaration) {
count += declaration.finishPatch();
});
constructors.forEach((String name, Declaration declaration) {
count += declaration.finishPatch();
});
return count;
}
List<Declaration> computeDirectSupertypes(ClassBuilder objectClass) {
final List<Declaration> result = <Declaration>[];
final KernelTypeBuilder supertype = this.supertype;
if (supertype != null) {
result.add(supertype.declaration);
} else if (objectClass != this) {
result.add(objectClass);
}
final List<KernelTypeBuilder> interfaces = this.interfaces;
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
KernelTypeBuilder interface = interfaces[i];
result.add(interface.declaration);
}
}
final KernelTypeBuilder mixedInType = this.mixedInType;
if (mixedInType != null) {
result.add(mixedInType.declaration);
}
return result;
}
@override
int compareTo(SourceClassBuilder other) {
int result = "$fileUri".compareTo("${other.fileUri}");
if (result != 0) return result;
return charOffset.compareTo(other.charOffset);
}
}