blob: 8aedb91c2fe7e1a8c30b29d20e9e5fe08fce1259 [file] [log] [blame]
// Copyright (c) 2017, 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 'dart:async';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/common.dart';
import 'package:compiler/src/common_elements.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/elements/elements.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/resolution/tree_elements.dart';
import 'package:compiler/src/tree/nodes.dart' as ast;
import 'package:expect/expect.dart';
import 'package:kernel/ast.dart' as ir;
import '../annotated_code_helper.dart';
import '../memory_compiler.dart';
import '../equivalence/id_equivalence.dart';
import '../kernel/compiler_helper.dart';
/// Function that compiles [code] with [options] and returns the [Compiler] object.
typedef Future<Compiler> CompileFunction(
AnnotatedCode code, Uri mainUri, List<String> options);
/// Function that computes a data mapping for [member].
///
/// Fills [actualMap] with the data and [sourceSpanMap] with the source spans
/// for the data origin.
typedef void ComputeMemberDataFunction(Compiler compiler, MemberEntity member,
Map<Id, String> actualMap, Map<Id, SourceSpan> sourceSpanMap,
{bool verbose});
/// Compile [code] from .dart sources.
Future<Compiler> compileFromSource(
AnnotatedCode code, Uri mainUri, List<String> options) async {
Compiler compiler = compilerFor(
memorySourceFiles: {'main.dart': code.sourceCode}, options: options);
compiler.stopAfterTypeInference = true;
await compiler.run(mainUri);
return compiler;
}
/// Compile [code] from .dill sources.
Future<Compiler> compileFromDill(
AnnotatedCode code, Uri mainUri, List<String> options) async {
Compiler compiler = await compileWithDill(
entryPoint: mainUri,
memorySourceFiles: {'main.dart': code.sourceCode},
options: [Flags.disableTypeInference]..addAll(options),
beforeRun: (Compiler compiler) {
compiler.stopAfterTypeInference = true;
});
return compiler;
}
/// Compute expected and actual data for all members defined in [annotatedCode].
///
/// Actual data is computed using [computeMemberData] and [code] is compiled
/// using [compileFunction].
Future<IdData> computeData(
String annotatedCode,
ComputeMemberDataFunction computeMemberData,
CompileFunction compileFunction,
{List<String> options: const <String>[],
bool verbose: false}) async {
AnnotatedCode code =
new AnnotatedCode.fromText(annotatedCode, commentStart, commentEnd);
Map<Id, String> expectedMap = computeExpectedMap(code);
Map<Id, String> actualMap = <Id, String>{};
Map<Id, SourceSpan> sourceSpanMap = <Id, SourceSpan>{};
Uri mainUri = Uri.parse('memory:main.dart');
Compiler compiler = await compileFunction(code, mainUri, options);
ElementEnvironment elementEnvironment =
compiler.backendClosedWorldForTesting.elementEnvironment;
LibraryEntity mainLibrary = elementEnvironment.mainLibrary;
elementEnvironment.forEachClass(mainLibrary, (ClassEntity cls) {
elementEnvironment.forEachClassMember(cls,
(ClassEntity declarer, MemberEntity member) {
if (cls == declarer) {
computeMemberData(compiler, member, actualMap, sourceSpanMap,
verbose: verbose);
}
});
});
elementEnvironment.forEachLibraryMember(mainLibrary, (MemberEntity member) {
computeMemberData(compiler, member, actualMap, sourceSpanMap,
verbose: verbose);
});
return new IdData(compiler, elementEnvironment, mainUri, expectedMap,
actualMap, sourceSpanMap);
}
/// Data collected by [computeData].
class IdData {
final Compiler compiler;
final ElementEnvironment elementEnvironment;
final Uri mainUri;
final Map<Id, String> expectedMap;
final Map<Id, String> actualMap;
final Map<Id, SourceSpan> sourceSpanMap;
IdData(this.compiler, this.elementEnvironment, this.mainUri, this.expectedMap,
this.actualMap, this.sourceSpanMap);
}
/// Compiles the [annotatedCode] with the provided [options] and calls
/// [computeMemberData] for each member. The result is checked against the
/// expected data derived from [annotatedCode].
Future checkCode(
String annotatedCode,
ComputeMemberDataFunction computeMemberData,
CompileFunction compileFunction,
{List<String> options: const <String>[],
bool verbose: false}) async {
IdData data = await computeData(
annotatedCode, computeMemberData, compileFunction,
options: options, verbose: verbose);
data.actualMap.forEach((Id id, String actual) {
if (!data.expectedMap.containsKey(id)) {
if (actual != '') {
reportHere(data.compiler.reporter, data.sourceSpanMap[id],
'Id $id not expected in ${data.expectedMap.keys}');
}
Expect.equals('', actual);
} else {
String expected = data.expectedMap.remove(id);
if (actual != expected) {
reportHere(data.compiler.reporter, data.sourceSpanMap[id],
'expected:${expected},actual:${actual}');
}
Expect.equals(expected, actual);
}
});
data.expectedMap.forEach((Id id, String expected) {
reportHere(
data.compiler.reporter,
computeSpannable(data.elementEnvironment, data.mainUri, id),
'expected:${expected},actual:null');
});
Expect.isTrue(
data.expectedMap.isEmpty, "Ids not found: ${data.expectedMap}.");
}
/// Compute a [Spannable] from an [id] in the library [mainUri].
Spannable computeSpannable(
ElementEnvironment elementEnvironment, Uri mainUri, Id id) {
if (id is NodeId) {
return new SourceSpan(mainUri, id.value, id.value + 1);
} else if (id is ElementId) {
LibraryEntity library = elementEnvironment.lookupLibrary(mainUri);
if (id.className != null) {
ClassEntity cls =
elementEnvironment.lookupClass(library, id.className, required: true);
return elementEnvironment.lookupClassMember(cls, id.memberName);
} else {
return elementEnvironment.lookupLibraryMember(library, id.memberName);
}
}
throw new UnsupportedError('Unsupported id $id.');
}
/// Compute the expectancy map from [code].
Map<Id, String> computeExpectedMap(AnnotatedCode code) {
Map<Id, String> map = <Id, String>{};
for (Annotation annotation in code.annotations) {
String text = annotation.text;
int colonPos = text.indexOf(':');
Id id;
String expected;
if (colonPos == -1) {
id = new NodeId(annotation.offset);
expected = text;
} else {
id = new ElementId(text.substring(0, colonPos));
expected = text.substring(colonPos + 1);
}
map[id] = expected;
}
return map;
}
/// Mixin used for computing [Id] data.
abstract class ComputerMixin {
Map<Id, String> get actualMap;
Map<Id, SourceSpan> get sourceSpanMap;
void registerValue(SourceSpan sourceSpan, Id id, String value) {
if (id != null && value != null) {
sourceSpanMap[id] = sourceSpan;
actualMap[id] = value;
}
}
}
/// Abstract AST visitor for computing [Id] data.
abstract class AbstractResolvedAstComputer extends ast.Visitor
with AstEnumeratorMixin, ComputerMixin {
final DiagnosticReporter reporter;
final Map<Id, String> actualMap;
final Map<Id, SourceSpan> sourceSpanMap;
final ResolvedAst resolvedAst;
AbstractResolvedAstComputer(
this.reporter, this.actualMap, this.sourceSpanMap, this.resolvedAst);
TreeElements get elements => resolvedAst.elements;
void computeForElement(AstElement element) {
ElementId id = computeElementId(element);
if (id == null) return;
String value = computeElementValue(element);
registerValue(element.sourcePosition, id, value);
}
void computeForNode(ast.Node node, AstElement element) {
NodeId id = computeNodeId(node, element);
if (id == null) return;
String value = computeNodeValue(node, element);
SourceSpan sourceSpan = new SourceSpan(resolvedAst.sourceUri,
node.getBeginToken().charOffset, node.getEndToken().charEnd);
registerValue(sourceSpan, id, value);
}
String computeElementValue(AstElement element);
String computeNodeValue(ast.Node node, AstElement element);
void run() {
resolvedAst.node.accept(this);
}
visitNode(ast.Node node) {
node.visitChildren(this);
}
visitVariableDefinitions(ast.VariableDefinitions node) {
for (ast.Node child in node.definitions) {
AstElement element = elements[child];
if (element == null) {
reportHere(reporter, child, 'No element for variable.');
} else if (!element.isLocal) {
computeForElement(element);
} else {
computeForNode(child, element);
}
}
visitNode(node);
}
visitFunctionExpression(ast.FunctionExpression node) {
AstElement element = elements.getFunctionDefinition(node);
if (!element.isLocal) {
computeForElement(element);
} else {
computeForNode(node, element);
}
visitNode(node);
}
visitSend(ast.Send node) {
computeForNode(node, null);
visitNode(node);
}
visitSendSet(ast.SendSet node) {
computeForNode(node, null);
visitNode(node);
}
}
/// Abstract IR visitor for computing [Id] data.
abstract class AbstractIrComputer extends ir.Visitor
with IrEnumeratorMixin, ComputerMixin {
final Map<Id, String> actualMap;
final Map<Id, SourceSpan> sourceSpanMap;
AbstractIrComputer(this.actualMap, this.sourceSpanMap);
void computeForMember(ir.Member member) {
ElementId id = computeElementId(member);
if (id == null) return;
String value = computeMemberValue(member);
registerValue(computeSpannable(member), id, value);
}
void computeForNode(ir.TreeNode node) {
NodeId id = computeNodeId(node);
if (id == null) return;
String value = computeNodeValue(node);
registerValue(computeSpannable(node), id, value);
}
Spannable computeSpannable(ir.TreeNode node) {
return new SourceSpan(
Uri.parse(node.location.file), node.fileOffset, node.fileOffset + 1);
}
String computeMemberValue(ir.Member member);
String computeNodeValue(ir.TreeNode node);
void run(ir.Node root) {
root.accept(this);
}
defaultNode(ir.Node node) {
node.visitChildren(this);
}
defaultMember(ir.Member node) {
computeForMember(node);
super.defaultMember(node);
}
visitMethodInvocation(ir.MethodInvocation node) {
computeForNode(node);
super.visitMethodInvocation(node);
}
visitPropertyGet(ir.PropertyGet node) {
computeForNode(node);
super.visitPropertyGet(node);
}
visitVariableDeclaration(ir.VariableDeclaration node) {
computeForNode(node);
super.visitVariableDeclaration(node);
}
visitFunctionDeclaration(ir.FunctionDeclaration node) {
computeForNode(node);
super.visitFunctionDeclaration(node);
}
visitFunctionExpression(ir.FunctionExpression node) {
computeForNode(node);
super.visitFunctionExpression(node);
}
}