blob: ea27be5df1e8f36028f7e6e2941c3998cddd74e4 [file] [log] [blame]
// Copyright (c) 2014, 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.
// Test of Compiler.forgetElement.
library trydart.forget_element_test;
import 'package:compiler/src/elements/elements.dart' show
AstElement,
ClassElement,
Element,
FunctionElement,
LocalFunctionElement,
MetadataAnnotation,
ScopeContainerElement,
VariableElement;
import 'package:compiler/src/js_backend/js_backend.dart' show
JavaScriptBackend;
import 'package:compiler/src/tree/tree.dart' as tree;
import 'package:compiler/src/scanner/scannerlib.dart' show
PartialMetadataAnnotation;
import 'package:compiler/src/elements/visitor.dart' show
ElementVisitor;
import 'package:compiler/src/dart2jslib.dart' show
DartConstantCompiler;
import 'package:compiler/src/universe/universe.dart' show
Universe;
import 'package:compiler/src/dart_types.dart' show
DartType;
import 'compiler_test_case.dart';
import 'forget_element_assertion.dart' show
assertUnimplementedLocalMetadata;
class ForgetElementTestCase extends CompilerTestCase {
final int expectedClosureCount;
final int expectedMetadataCount;
final int expectedConstantCount;
final int expectedInitialValueCount;
final int expectedInitialDartValueCount;
final int additionalClosureClassMaps;
JavaScriptBackend get backend => compiler.backend;
DartConstantCompiler get dartConstants =>
backend.constantCompilerTask.dartConstantCompiler;
Universe get codegenUniverse => compiler.enqueuer.codegen.universe;
Universe get resolutionUniverse => compiler.enqueuer.resolution.universe;
ForgetElementTestCase(
String source,
{int closureCount: 0,
int metadataCount: 0,
int constantCount: 0,
int initialValueCount: 0,
int initialDartValueCount: null,
this.additionalClosureClassMaps: 0})
: this.expectedClosureCount = closureCount,
this.expectedMetadataCount = metadataCount,
this.expectedConstantCount = constantCount,
this.expectedInitialValueCount = initialValueCount,
// Sometimes these numbers aren't the same. Appears to happen with
// non-const fields, because those aren't compile-time constants in the
// strict language specification sense.
this.expectedInitialDartValueCount = (initialDartValueCount == null)
? initialValueCount : initialDartValueCount,
super(source);
Future run() => compile().then((LibraryElement library) {
// Check that the compiler has recorded the expected number of closures.
Expect.equals(
expectedClosureCount, closuresInLibrary(library).length,
'closure count');
// Check that the compiler has recorded the expected number of metadata
// annotations.
Expect.equals(
expectedMetadataCount, metadataInLibrary(library).length,
'metadata count');
// Check that the compiler has recorded the expected number of
// constants. Since metadata is also constants, those must also be counted.
Expect.equals(
expectedConstantCount + expectedMetadataCount,
constantsIn(library).length,
'constant count');
// Check that the compiler has recorded the expected number of initial
// values.
Expect.equals(
expectedInitialValueCount,
elementsWithJsInitialValuesIn(library).length,
'number of fields with initial values (JS)');
Expect.equals(
expectedInitialDartValueCount,
elementsWithDartInitialValuesIn(library).length,
'number of fields with initial values (Dart)');
// Check that the compiler has recorded the expected number of closure
// class maps. There's always at least one, from main. Each top-level
// element also seems to induce one.
Expect.equals(
expectedClosureCount + additionalClosureClassMaps,
closureClassMapsIn(library).length - 1,
'closure class map count ${closureClassMapsIn(library)}');
// Forget about all elements.
library.forEachLocalMember(compiler.forgetElement);
// Check that all the closures were forgotten.
Expect.isTrue(closuresInLibrary(library).isEmpty, 'closures');
// Check that the metadata annotations were forgotten.
Expect.isTrue(metadataInLibrary(library).isEmpty, 'metadata');
// Check that the constants were forgotten.
Expect.isTrue(constantsIn(library).isEmpty, 'constants');
// Check that initial values were forgotten.
Expect.isTrue(
elementsWithJsInitialValuesIn(library).isEmpty,
'fields with initial values (JS)');
Expect.isTrue(
elementsWithDartInitialValuesIn(library).isEmpty,
'fields with initial values (Dart)');
// Check that closure class maps were forgotten.
Expect.isTrue(closureClassMapsIn(library).isEmpty, 'closure class maps');
// Check that istantiated types and classes were forgotten.
Expect.isTrue(
resolutionTypesIn(library).isEmpty, 'resolution instantiatedTypes');
Expect.isTrue(
resolutionClassesIn(library).isEmpty, 'resolution instantiatedClasses');
Expect.isTrue(
codegenTypesIn(library).isEmpty, 'codegen instantiatedTypes');
Expect.isTrue(
codegenClassesIn(library).isEmpty, 'codegen instantiatedClasses');
// Check that other members remembered by [Universe] were forgotten.
Expect.isTrue(
resolutionMembersIn(library).isEmpty, 'resolution misc members');
Expect.isTrue(
codegenMembersIn(library).isEmpty, 'codegen misc members');
// Check that classes remembered by the enqueuer have been forgotten.
Expect.isTrue(
codegenSeenClassesIn(library).isEmpty, 'codegen seen classes');
Expect.isTrue(
resolutionSeenClassesIn(library).isEmpty, 'resolution seen classes');
});
Iterable closuresInLibrary(LibraryElement library) {
return compiler.enqueuer.resolution.universe.allClosures.where(
(LocalFunctionElement closure) => closure.library == library);
}
Iterable metadataInLibrary(LibraryElement library) {
return backend.constants.metadataConstantMap.keys.where(
(MetadataAnnotation metadata) {
return metadata.annotatedElement.library == library;
});
}
Iterable<tree.Node> nodesIn(LibraryElement library) {
NodeCollector collector = new NodeCollector();
library.forEachLocalMember((e) {
if (e is AstElement && e.hasNode) {
e.node.accept(collector);
}
// Due to quirks of history, only parameter metadata is recorded in AST
// nodes, so they must be extracted from the elements.
for (MetadataAnnotation metadata in e.metadata) {
if (metadata is PartialMetadataAnnotation) {
if (metadata.cachedNode != null) {
metadata.cachedNode.accept(collector);
}
}
}
});
List<MetadataAnnotation> metadata =
(new MetadataCollector()..visit(library)).metadata;
return collector.nodes;
}
Iterable constantsIn(LibraryElement library) {
return nodesIn(library)
.map((node) => backend.constants.nodeConstantMap[node])
.where((constant) => constant != null);
}
Iterable elementsWithJsInitialValuesIn(LibraryElement library) {
return backend.constants.initialVariableValues.keys.where(
(VariableElement element) => element.library == library);
}
Iterable elementsWithDartInitialValuesIn(LibraryElement library) {
return dartConstants.initialVariableValues.keys.where(
(VariableElement element) => element.library == library);
}
Iterable closureClassMapsIn(LibraryElement library) {
Map cache = compiler.closureToClassMapper.closureMappingCache;
return nodesIn(library).where((node) => cache[node] != null);
}
Iterable codegenTypesIn(LibraryElement library) {
return codegenUniverse.instantiatedTypes.where(
(DartType type) => type.element.library == library);
}
Iterable codegenClassesIn(LibraryElement library) {
return codegenUniverse.directlyInstantiatedClasses.where(
(ClassElement cls) => cls.library == library);
}
Iterable codegenMembersIn(LibraryElement library) {
sameLibrary(e) => e.library == library;
return new Set()
..addAll(codegenUniverse.closurizedMembers.where(sameLibrary))
..addAll(codegenUniverse.fieldSetters.where(sameLibrary))
..addAll(codegenUniverse.fieldGetters.where(sameLibrary));
}
Iterable resolutionTypesIn(LibraryElement library) {
return resolutionUniverse.instantiatedTypes.where(
(DartType type) => type.element.library == library);
}
Iterable resolutionClassesIn(LibraryElement library) {
return resolutionUniverse.directlyInstantiatedClasses.where(
(ClassElement cls) => cls.library == library);
}
Iterable resolutionMembersIn(LibraryElement library) {
sameLibrary(e) => e.library == library;
return new Set()
..addAll(resolutionUniverse.closurizedMembers.where(sameLibrary))
..addAll(resolutionUniverse.fieldSetters.where(sameLibrary))
..addAll(resolutionUniverse.fieldGetters.where(sameLibrary));
}
Iterable codegenSeenClassesIn(LibraryElement library) {
return compiler.codegenWorld.allInstantiatedClasses.where(
(e) => e.library == library);
}
Iterable resolutionSeenClassesIn(LibraryElement library) {
return compiler.resolverWorld.allInstantiatedClasses.where(
(e) => e.library == library);
}
}
class NodeCollector extends tree.Visitor {
final List<tree.Node> nodes = <tree.Node>[];
void visitNode(tree.Node node) {
nodes.add(node);
node.visitChildren(this);
}
}
class MetadataCollector extends ElementVisitor {
final List<MetadataAnnotation> metadata = <MetadataAnnotation>[];
void visitElement(Element e) {
metadata.addAll(e.metadata.toList());
}
void visitScopeContainerElement(ScopeContainerElement e) {
super.visitScopeContainerElement(e);
e.forEachLocalMember(this.visit);
}
void visitFunctionElement(FunctionElement e) {
super.visitFunctionElement(e);
if (e.hasFunctionSignature) {
e.functionSignature.forEachParameter(this.visit);
}
}
}
void main() {
runTests(tests);
}
List<CompilerTestCase> get tests => <CompilerTestCase>[
// Edge case: empty body.
new ForgetElementTestCase(
'main() {}'),
// Edge case: simple arrow function.
new ForgetElementTestCase(
'main() => null;'),
// Test that a local closure is discarded correctly.
new ForgetElementTestCase(
'main() => (() => null)();',
closureCount: 1),
// Test that nested closures are discarded correctly.
new ForgetElementTestCase(
'main() => (() => (() => null)())();',
closureCount: 2),
// Test that nested closures are discarded correctly.
new ForgetElementTestCase(
'main() => (() => (() => (() => null)())())();',
closureCount: 3),
// Test that metadata on top-level function is discarded correctly.
new ForgetElementTestCase(
'@Constant() main() => null; $CONSTANT_CLASS',
metadataCount: 1),
// Test that metadata on top-level variable is discarded correctly.
new ForgetElementTestCase(
'@Constant() var x; main() => x; $CONSTANT_CLASS',
metadataCount: 1,
initialValueCount: 1,
initialDartValueCount: 0),
// Test that metadata on parameter on a local function is discarded
// correctly.
new ForgetElementTestCase(
'main() => ((@Constant() x) => x)(null); $CONSTANT_CLASS',
closureCount: 1,
metadataCount: 1),
// Test that a constant in a top-level method body is discarded
// correctly.
new ForgetElementTestCase(
'main() => const Constant(); $CONSTANT_CLASS',
constantCount: 1),
// Test that a constant in a nested function body is discarded
// correctly.
new ForgetElementTestCase(
'main() => (() => const Constant())(); $CONSTANT_CLASS',
constantCount: 1,
closureCount: 1),
// Test that a constant in a nested function body is discarded
// correctly.
new ForgetElementTestCase(
'main() => (() => (() => const Constant())())(); $CONSTANT_CLASS',
constantCount: 1,
closureCount: 2),
// Test that a constant in a top-level variable initializer is
// discarded correctly.
new ForgetElementTestCase(
'main() => x; var x = const Constant(); $CONSTANT_CLASS',
constantCount: 1,
initialValueCount: 1,
initialDartValueCount: 0,
additionalClosureClassMaps: 1),
// Test that a constant in a parameter initializer is discarded
// correctly.
new ForgetElementTestCase(
'main([x = const Constant()]) => x; $CONSTANT_CLASS',
constantCount: 1,
initialValueCount: 1),
// Test that a constant in a parameter initializer is discarded
// correctly (nested function).
new ForgetElementTestCase(
'main() => (([x = const Constant()]) => x)(); $CONSTANT_CLASS',
closureCount: 1,
constantCount: 1,
initialValueCount: 1),
// Test that a constant in a parameter initializer is discarded
// correctly (deeply nested function).
new ForgetElementTestCase(
'main() => (() => (([x = const Constant()]) => x)())();'
' $CONSTANT_CLASS',
closureCount: 2,
constantCount: 1,
initialValueCount: 1),
// TODO(ahe): Add test for super sends [backend.aliasedSuperMembers].
]..addAll(assertUnimplementedLocalMetadata());