blob: d32d8f54c843f5880901c5438976240395d9f6ab [file] [log] [blame]
// Copyright (c) 2019, 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.
import 'package:kernel/ast.dart';
import '../fasta/builder/builder.dart';
import '../fasta/builder/extension_builder.dart';
import '../fasta/kernel/kernel_builder.dart';
import '../fasta/messages.dart';
import '../fasta/source/source_library_builder.dart';
import '../fasta/source/source_loader.dart';
import '../kernel_generator_impl.dart';
/// Helper methods to use in annotated tests.
/// Returns a canonical simple name for [member].
String getMemberName(Member member) {
if (member is Procedure && member.isSetter) return '${member.name.name}=';
return member.name.name;
}
/// Returns the enclosing [Member] for [node].
Member getEnclosingMember(TreeNode node) {
while (node is! Member) {
node = node.parent;
}
return node;
}
/// Finds the first [Library] in [component] with the given import [uri].
///
/// If [required] is `true` an error is thrown if no library was found.
Library lookupLibrary(Component component, Uri uri, {bool required: true}) {
return component.libraries
.firstWhere((Library library) => library.importUri == uri, orElse: () {
if (required) {
throw new ArgumentError("Library '$uri' not found.");
}
return null;
});
}
/// Finds the first [Class] in [library] with the given [className].
///
/// If [required] is `true` an error is thrown if no class was found.
Class lookupClass(Library library, String className, {bool required: true}) {
return library.classes.firstWhere((Class cls) => cls.name == className,
orElse: () {
if (required) {
throw new ArgumentError("Class '$className' not found in '$library'.");
}
return null;
});
}
/// Finds the first [Extension] in [library] with the given [className].
///
/// If [required] is `true` an error is thrown if no class was found.
Extension lookupExtension(Library library, String extensionName,
{bool required: true}) {
return library.extensions.firstWhere(
(Extension extension) => extension.name == extensionName, orElse: () {
if (required) {
throw new ArgumentError(
"Extension '$extensionName' not found in '$library'.");
}
return null;
});
}
/// Finds the first [Member] in [library] with the given canonical simple
/// [memberName] as computed by [getMemberName].
///
/// If [required] is `true` an error is thrown if no member was found.
Member lookupLibraryMember(Library library, String memberName,
{bool required: true}) {
return library.members.firstWhere(
(Member member) => getMemberName(member) == memberName, orElse: () {
if (required) {
throw new ArgumentError("Member '$memberName' not found in '$library'.");
}
return null;
});
}
/// Finds the first [Member] in [cls] with the given canonical simple
/// [memberName] as computed by [getMemberName].
///
/// If [required] is `true` an error is thrown if no member was found.
Member lookupClassMember(Class cls, String memberName, {bool required: true}) {
return cls.members.firstWhere(
(Member member) => getMemberName(member) == memberName, orElse: () {
if (required) {
throw new ArgumentError("Member '$memberName' not found in '$cls'.");
}
return null;
});
}
LibraryBuilder lookupLibraryBuilder(
InternalCompilerResult compilerResult, Library library,
{bool required: true}) {
SourceLoader loader = compilerResult.kernelTargetForTesting.loader;
SourceLibraryBuilder builder = loader.builders[library.importUri];
if (builder == null && required) {
throw new ArgumentError("DeclarationBuilder for $library not found.");
}
return builder;
}
TypeParameterScopeBuilder lookupLibraryDeclarationBuilder(
InternalCompilerResult compilerResult, Library library,
{bool required: true}) {
SourceLibraryBuilder builder =
lookupLibraryBuilder(compilerResult, library, required: required);
return builder.libraryDeclaration;
}
ClassBuilder lookupClassBuilder(
InternalCompilerResult compilerResult, Class cls,
{bool required: true}) {
TypeParameterScopeBuilder libraryBuilder = lookupLibraryDeclarationBuilder(
compilerResult, cls.enclosingLibrary,
required: required);
ClassBuilder clsBuilder = libraryBuilder.members[cls.name];
if (clsBuilder == null && required) {
throw new ArgumentError("ClassBuilder for $cls not found.");
}
return clsBuilder;
}
ExtensionBuilder lookupExtensionBuilder(
InternalCompilerResult compilerResult, Extension extension,
{bool required: true}) {
TypeParameterScopeBuilder libraryBuilder = lookupLibraryDeclarationBuilder(
compilerResult, extension.enclosingLibrary,
required: required);
ExtensionBuilder extensionBuilder = libraryBuilder.members[extension.name];
if (extensionBuilder == null && required) {
throw new ArgumentError("ExtensionBuilder for $extension not found.");
}
return extensionBuilder;
}
/// Look up the [MemberBuilder] for [member] through the [ClassBuilder] for
/// [cls] using [memberName] as its name.
MemberBuilder lookupClassMemberBuilder(InternalCompilerResult compilerResult,
Class cls, Member member, String memberName,
{bool required: true}) {
ClassBuilder classBuilder =
lookupClassBuilder(compilerResult, cls, required: required);
MemberBuilder memberBuilder;
if (classBuilder != null) {
if (member is Constructor) {
memberBuilder = classBuilder.constructors.local[memberName];
} else if (member is Procedure && member.isSetter) {
memberBuilder = classBuilder.scope.setters[memberName];
} else {
memberBuilder = classBuilder.scope.local[memberName];
}
}
if (memberBuilder == null && required) {
throw new ArgumentError("MemberBuilder for $member not found.");
}
return memberBuilder;
}
MemberBuilder lookupMemberBuilder(
InternalCompilerResult compilerResult, Member member,
{bool required: true}) {
MemberBuilder memberBuilder;
if (member.isExtensionMember) {
String memberName = member.name.name;
String extensionName = memberName.substring(0, memberName.indexOf('|'));
memberName = memberName.substring(extensionName.length + 1);
bool isSetter = member is Procedure && member.isSetter;
if (memberName.startsWith('set#')) {
memberName = memberName.substring(4);
isSetter = true;
} else if (memberName.startsWith('get#')) {
memberName = memberName.substring(4);
}
Extension extension =
lookupExtension(member.enclosingLibrary, extensionName);
memberBuilder = lookupExtensionMemberBuilder(
compilerResult, extension, member, memberName,
isSetter: isSetter, required: required);
} else if (member.enclosingClass != null) {
memberBuilder = lookupClassMemberBuilder(
compilerResult, member.enclosingClass, member, member.name.name,
required: required);
} else {
TypeParameterScopeBuilder libraryBuilder = lookupLibraryDeclarationBuilder(
compilerResult, member.enclosingLibrary);
if (member is Procedure && member.isSetter) {
memberBuilder = libraryBuilder.members[member.name.name];
} else {
memberBuilder = libraryBuilder.setters[member.name.name];
}
}
if (memberBuilder == null && required) {
throw new ArgumentError("MemberBuilder for $member not found.");
}
return memberBuilder;
}
/// Look up the [MemberBuilder] for [member] through the [ExtensionBuilder] for
/// [extension] using [memberName] as its name.
MemberBuilder lookupExtensionMemberBuilder(
InternalCompilerResult compilerResult,
Extension extension,
Member member,
String memberName,
{bool isSetter: false,
bool required: true}) {
ExtensionBuilder extensionBuilder =
lookupExtensionBuilder(compilerResult, extension, required: required);
MemberBuilder memberBuilder;
if (extensionBuilder != null) {
if (isSetter) {
memberBuilder = extensionBuilder.scope.setters[memberName];
} else {
memberBuilder = extensionBuilder.scope.local[memberName];
}
}
if (memberBuilder == null && required) {
throw new ArgumentError("MemberBuilder for $member not found.");
}
return memberBuilder;
}
/// Returns a textual representation of the constant [node] to be used in
/// testing.
String constantToText(Constant node) {
StringBuffer sb = new StringBuffer();
new ConstantToTextVisitor(sb).visit(node);
return sb.toString();
}
/// Returns a textual representation of the type [node] to be used in
/// testing.
String typeToText(DartType node) {
StringBuffer sb = new StringBuffer();
new DartTypeToTextVisitor(sb).visit(node);
return sb.toString();
}
class ConstantToTextVisitor implements ConstantVisitor<void> {
final StringBuffer sb;
final DartTypeToTextVisitor typeToText;
ConstantToTextVisitor(this.sb) : typeToText = new DartTypeToTextVisitor(sb);
void visit(Constant node) => node.accept(this);
void visitList(Iterable<Constant> nodes) {
String comma = '';
for (Constant node in nodes) {
sb.write(comma);
visit(node);
comma = ',';
}
}
void defaultConstant(Constant node) => throw new UnimplementedError(
'Unexpected constant $node (${node.runtimeType})');
void visitNullConstant(NullConstant node) {
sb.write('Null()');
}
void visitBoolConstant(BoolConstant node) {
sb.write('Bool(${node.value})');
}
void visitIntConstant(IntConstant node) {
sb.write('Int(${node.value})');
}
void visitDoubleConstant(DoubleConstant node) {
sb.write('Double(${node.value})');
}
void visitStringConstant(StringConstant node) {
sb.write('String(${node.value})');
}
void visitSymbolConstant(SymbolConstant node) {
sb.write('Symbol(${node.name})');
}
void visitMapConstant(MapConstant node) {
sb.write('Map<');
typeToText.visit(node.keyType);
sb.write(',');
typeToText.visit(node.valueType);
sb.write('>(');
String comma = '';
for (ConstantMapEntry entry in node.entries) {
sb.write(comma);
entry.key.accept(this);
sb.write(':');
entry.value.accept(this);
comma = ',';
}
sb.write(')');
}
void visitListConstant(ListConstant node) {
sb.write('List<');
typeToText.visit(node.typeArgument);
sb.write('>(');
visitList(node.entries);
sb.write(')');
}
void visitSetConstant(SetConstant node) {
sb.write('Set<');
typeToText.visit(node.typeArgument);
sb.write('>(');
visitList(node.entries);
sb.write(')');
}
void visitInstanceConstant(InstanceConstant node) {
sb.write('Instance(');
sb.write(node.classNode.name);
if (node.typeArguments.isNotEmpty) {
sb.write('<');
typeToText.visitList(node.typeArguments);
sb.write('>');
}
if (node.fieldValues.isNotEmpty) {
sb.write(',{');
String comma = '';
for (Reference ref in node.fieldValues.keys) {
sb.write(comma);
sb.write(getMemberName(ref.asField));
sb.write(':');
visit(node.fieldValues[ref]);
comma = ',';
}
sb.write('}');
}
sb.write(')');
}
void visitPartialInstantiationConstant(PartialInstantiationConstant node) {
sb.write('Instantiation(');
sb.write(getMemberName(node.tearOffConstant.procedure));
sb.write('<');
typeToText.visitList(node.types);
sb.write('>)');
}
void visitTearOffConstant(TearOffConstant node) {
sb.write('Function(');
sb.write(getMemberName(node.procedure));
sb.write(')');
}
void visitTypeLiteralConstant(TypeLiteralConstant node) {
sb.write('TypeLiteral(');
typeToText.visit(node.type);
sb.write(')');
}
void visitUnevaluatedConstant(UnevaluatedConstant node) {
sb.write('Unevaluated()');
}
}
class DartTypeToTextVisitor implements DartTypeVisitor<void> {
final StringBuffer sb;
DartTypeToTextVisitor(this.sb);
void visit(DartType node) => node.accept(this);
void visitList(Iterable<DartType> nodes) {
String comma = '';
for (DartType node in nodes) {
sb.write(comma);
visit(node);
comma = ',';
}
}
void defaultDartType(DartType node) => throw new UnimplementedError(
'Unexpected type $node (${node.runtimeType})');
void visitInvalidType(InvalidType node) {
sb.write('<invalid>');
}
void visitDynamicType(DynamicType node) {
sb.write('dynamic');
}
void visitVoidType(VoidType node) {
sb.write('void');
}
void visitBottomType(BottomType node) {
sb.write('<bottom>');
}
void visitInterfaceType(InterfaceType node) {
sb.write(node.classNode.name);
if (node.typeArguments.isNotEmpty) {
sb.write('<');
visitList(node.typeArguments);
sb.write('>');
}
}
void visitFunctionType(FunctionType node) {
sb.write('(');
String comma = '';
visitList(node.positionalParameters.take(node.requiredParameterCount));
if (node.requiredParameterCount > 0) {
comma = ',';
}
if (node.requiredParameterCount < node.positionalParameters.length) {
sb.write(comma);
sb.write('[');
visitList(node.positionalParameters.skip(node.requiredParameterCount));
sb.write(']');
comma = ',';
}
if (node.namedParameters.isNotEmpty) {
sb.write(comma);
sb.write('{');
comma = '';
for (NamedType namedParameter in node.namedParameters) {
sb.write(comma);
visit(namedParameter.type);
sb.write(' ');
sb.write(namedParameter.name);
comma = ',';
}
sb.write('}');
}
sb.write(')->');
visit(node.returnType);
}
void visitTypeParameterType(TypeParameterType node) {
sb.write(node.parameter.name);
if (node.promotedBound != null) {
sb.write(' extends ');
visit(node.promotedBound);
}
}
void visitTypedefType(TypedefType node) {
sb.write(node.typedefNode.name);
if (node.typeArguments.isNotEmpty) {
sb.write('<');
visitList(node.typeArguments);
sb.write('>');
}
}
}
/// Returns `true` if [type] is `Object` from `dart:core`.
bool isObject(DartType type) {
return type is InterfaceType &&
type.classNode.name == 'Object' &&
'${type.classNode.enclosingLibrary.importUri}' == 'dart:core';
}
/// Returns a textual representation of the [typeParameter] to be used in
/// testing.
String typeParameterToText(TypeParameter typeParameter) {
String name = typeParameter.name;
if (!isObject(typeParameter.bound)) {
return '$name extends ${typeToText(typeParameter.bound)}';
}
return name;
}
/// Returns a textual representation of the [type] to be used in testing.
String typeBuilderToText(TypeBuilder type) {
StringBuffer sb = new StringBuffer();
_typeBuilderToText(type, sb);
return sb.toString();
}
void _typeBuilderToText(TypeBuilder type, StringBuffer sb) {
if (type is NamedTypeBuilder) {
sb.write(type.name);
if (type.arguments != null && type.arguments.isNotEmpty) {
sb.write('<');
_typeBuildersToText(type.arguments, sb);
sb.write('>');
}
} else {
throw 'Unhandled type builder $type (${type.runtimeType})';
}
}
void _typeBuildersToText(Iterable<TypeBuilder> types, StringBuffer sb) {
String comma = '';
for (TypeBuilder type in types) {
sb.write(comma);
_typeBuilderToText(type, sb);
comma = ',';
}
}
/// Returns a textual representation of the [typeVariable] to be used in
/// testing.
String typeVariableBuilderToText(TypeVariableBuilder typeVariable) {
String name = typeVariable.name;
if (typeVariable.bound != null) {
return '$name extends ${typeBuilderToText(typeVariable.bound)}';
}
return name;
}
/// Returns a textual representation of [errors] to be used in testing.
String errorsToText(List<FormattedMessage> errors) {
return errors.map((m) => m.message).join(',');
}
/// Returns a textual representation of [descriptor] to be used in testing.
String extensionMethodDescriptorToText(ExtensionMemberDescriptor descriptor) {
StringBuffer sb = new StringBuffer();
if (descriptor.isExternal) {
sb.write('external ');
}
if (descriptor.isStatic) {
sb.write('static ');
}
switch (descriptor.kind) {
case ExtensionMemberKind.Method:
break;
case ExtensionMemberKind.Getter:
sb.write('getter ');
break;
case ExtensionMemberKind.Setter:
sb.write('setter ');
break;
case ExtensionMemberKind.Operator:
sb.write('operator ');
break;
case ExtensionMemberKind.Field:
sb.write('field ');
break;
case ExtensionMemberKind.TearOff:
sb.write('tearoff ');
break;
}
sb.write(descriptor.name.name);
sb.write('=');
Member member = descriptor.member.asMember;
String name = member.name.name;
if (member is Procedure && member.isSetter) {
sb.write('$name=');
} else {
sb.write(name);
}
return sb.toString();
}