mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:27:43 +00:00
dart2js: add initial support for lookup-maps
R=herhut@google.com, sra@google.com Review URL: https://codereview.chromium.org//1320503003 .
This commit is contained in:
parent
e30f7638e2
commit
2c660aa2f0
|
@ -339,6 +339,10 @@ abstract class Backend {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called after the queue is closed. [onQueueEmpty] may be called multiple
|
||||||
|
/// times, but [onQueueClosed] is only called once.
|
||||||
|
void onQueueClosed() {}
|
||||||
|
|
||||||
/// Called after [element] has been resolved.
|
/// Called after [element] has been resolved.
|
||||||
// TODO(johnniwinther): Change [TreeElements] to [Registry] or a dependency
|
// TODO(johnniwinther): Change [TreeElements] to [Registry] or a dependency
|
||||||
// node. [elements] is currently unused by the implementation.
|
// node. [elements] is currently unused by the implementation.
|
||||||
|
|
|
@ -112,7 +112,6 @@ class CodegenRegistry extends Registry {
|
||||||
|
|
||||||
void registerCompileTimeConstant(ConstantValue constant) {
|
void registerCompileTimeConstant(ConstantValue constant) {
|
||||||
backend.registerCompileTimeConstant(constant, this);
|
backend.registerCompileTimeConstant(constant, this);
|
||||||
backend.constants.addCompileTimeConstantForEmission(constant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerTypeVariableBoundsSubtypeCheck(DartType subtype,
|
void registerTypeVariableBoundsSubtypeCheck(DartType subtype,
|
||||||
|
@ -146,6 +145,7 @@ class CodegenRegistry extends Registry {
|
||||||
|
|
||||||
void registerTypeConstant(ClassElement element) {
|
void registerTypeConstant(ClassElement element) {
|
||||||
backend.customElementsAnalysis.registerTypeConstant(element, world);
|
backend.customElementsAnalysis.registerTypeConstant(element, world);
|
||||||
|
backend.lookupMapAnalysis.registerTypeConstant(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerStaticInvocation(Element element) {
|
void registerStaticInvocation(Element element) {
|
||||||
|
|
|
@ -1240,6 +1240,7 @@ abstract class Compiler implements DiagnosticListener {
|
||||||
}
|
}
|
||||||
emptyQueue(world);
|
emptyQueue(world);
|
||||||
world.queueIsClosed = true;
|
world.queueIsClosed = true;
|
||||||
|
backend.onQueueClosed();
|
||||||
assert(compilationFailed || world.checkNoEnqueuedInvokedInstanceMethods());
|
assert(compilationFailed || world.checkNoEnqueuedInvokedInstanceMethods());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -233,6 +233,8 @@ class JavaScriptBackend extends Backend {
|
||||||
new Uri(scheme: 'dart', path: '_js_embedded_names');
|
new Uri(scheme: 'dart', path: '_js_embedded_names');
|
||||||
static final Uri DART_ISOLATE_HELPER =
|
static final Uri DART_ISOLATE_HELPER =
|
||||||
new Uri(scheme: 'dart', path: '_isolate_helper');
|
new Uri(scheme: 'dart', path: '_isolate_helper');
|
||||||
|
static final Uri PACKAGE_LOOKUP_MAP =
|
||||||
|
new Uri(scheme: 'package', path: 'lookup_map/lookup_map.dart');
|
||||||
|
|
||||||
static const String INVOKE_ON = '_getCachedInvocation';
|
static const String INVOKE_ON = '_getCachedInvocation';
|
||||||
static const String START_ROOT_ISOLATE = 'startRootIsolate';
|
static const String START_ROOT_ISOLATE = 'startRootIsolate';
|
||||||
|
@ -608,6 +610,9 @@ class JavaScriptBackend extends Backend {
|
||||||
/// constructors for custom elements.
|
/// constructors for custom elements.
|
||||||
CustomElementsAnalysis customElementsAnalysis;
|
CustomElementsAnalysis customElementsAnalysis;
|
||||||
|
|
||||||
|
/// Codegen support for tree-shaking entries of `LookupMap`.
|
||||||
|
LookupMapAnalysis lookupMapAnalysis;
|
||||||
|
|
||||||
/// Support for classifying `noSuchMethod` implementations.
|
/// Support for classifying `noSuchMethod` implementations.
|
||||||
NoSuchMethodRegistry noSuchMethodRegistry;
|
NoSuchMethodRegistry noSuchMethodRegistry;
|
||||||
|
|
||||||
|
@ -641,6 +646,7 @@ class JavaScriptBackend extends Backend {
|
||||||
compiler, namer, generateSourceMap, useStartupEmitter);
|
compiler, namer, generateSourceMap, useStartupEmitter);
|
||||||
typeVariableHandler = new TypeVariableHandler(compiler);
|
typeVariableHandler = new TypeVariableHandler(compiler);
|
||||||
customElementsAnalysis = new CustomElementsAnalysis(this);
|
customElementsAnalysis = new CustomElementsAnalysis(this);
|
||||||
|
lookupMapAnalysis = new LookupMapAnalysis(this);
|
||||||
noSuchMethodRegistry = new NoSuchMethodRegistry(this);
|
noSuchMethodRegistry = new NoSuchMethodRegistry(this);
|
||||||
constantCompilerTask = new JavaScriptConstantTask(compiler);
|
constantCompilerTask = new JavaScriptConstantTask(compiler);
|
||||||
resolutionCallbacks = new JavaScriptResolutionCallbacks(this);
|
resolutionCallbacks = new JavaScriptResolutionCallbacks(this);
|
||||||
|
@ -949,11 +955,23 @@ class JavaScriptBackend extends Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerCompileTimeConstant(ConstantValue constant, Registry registry) {
|
void registerCompileTimeConstant(ConstantValue constant, Registry registry,
|
||||||
|
{bool addForEmission: true}) {
|
||||||
registerCompileTimeConstantInternal(constant, registry);
|
registerCompileTimeConstantInternal(constant, registry);
|
||||||
for (ConstantValue dependency in constant.getDependencies()) {
|
|
||||||
registerCompileTimeConstant(dependency, registry);
|
if (!registry.isForResolution &&
|
||||||
|
lookupMapAnalysis.isLookupMap(constant)) {
|
||||||
|
// Note: internally, this registration will temporarily remove the
|
||||||
|
// constant dependencies and add them later on-demand.
|
||||||
|
lookupMapAnalysis.registerLookupMapReference(constant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ConstantValue dependency in constant.getDependencies()) {
|
||||||
|
registerCompileTimeConstant(dependency, registry,
|
||||||
|
addForEmission: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addForEmission) constants.addCompileTimeConstantForEmission(constant);
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerCompileTimeConstantInternal(ConstantValue constant,
|
void registerCompileTimeConstantInternal(ConstantValue constant,
|
||||||
|
@ -971,6 +989,11 @@ class JavaScriptBackend extends Backend {
|
||||||
} else if (constant.isType) {
|
} else if (constant.isType) {
|
||||||
enqueueInResolution(getCreateRuntimeType(), registry);
|
enqueueInResolution(getCreateRuntimeType(), registry);
|
||||||
registry.registerInstantiation(typeImplementation.rawType);
|
registry.registerInstantiation(typeImplementation.rawType);
|
||||||
|
TypeConstantValue typeConstant = constant;
|
||||||
|
DartType representedType = typeConstant.representedType;
|
||||||
|
if (representedType != const DynamicType()) {
|
||||||
|
lookupMapAnalysis.registerTypeConstant(representedType.element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -996,7 +1019,7 @@ class JavaScriptBackend extends Backend {
|
||||||
Registry registry) {
|
Registry registry) {
|
||||||
assert(registry.isForResolution);
|
assert(registry.isForResolution);
|
||||||
ConstantValue constant = constants.getConstantValueForMetadata(metadata);
|
ConstantValue constant = constants.getConstantValueForMetadata(metadata);
|
||||||
registerCompileTimeConstant(constant, registry);
|
registerCompileTimeConstant(constant, registry, addForEmission: false);
|
||||||
metadataConstants.add(new Dependency(constant, annotatedElement));
|
metadataConstants.add(new Dependency(constant, annotatedElement));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1129,6 +1152,13 @@ class JavaScriptBackend extends Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
customElementsAnalysis.registerInstantiatedClass(cls, enqueuer);
|
customElementsAnalysis.registerInstantiatedClass(cls, enqueuer);
|
||||||
|
if (!enqueuer.isResolutionQueue) {
|
||||||
|
lookupMapAnalysis.registerInstantiatedClass(cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerInstantiatedType(InterfaceType type, Registry registry) {
|
||||||
|
lookupMapAnalysis.registerInstantiatedType(type, registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerUseInterceptor(Enqueuer enqueuer) {
|
void registerUseInterceptor(Enqueuer enqueuer) {
|
||||||
|
@ -1438,7 +1468,6 @@ class JavaScriptBackend extends Backend {
|
||||||
constants.getConstantValueForVariable(element);
|
constants.getConstantValueForVariable(element);
|
||||||
if (initialValue != null) {
|
if (initialValue != null) {
|
||||||
registerCompileTimeConstant(initialValue, work.registry);
|
registerCompileTimeConstant(initialValue, work.registry);
|
||||||
constants.addCompileTimeConstantForEmission(initialValue);
|
|
||||||
// We don't need to generate code for static or top-level
|
// We don't need to generate code for static or top-level
|
||||||
// variables. For instance variables, we may need to generate
|
// variables. For instance variables, we may need to generate
|
||||||
// the checked setter.
|
// the checked setter.
|
||||||
|
@ -2133,6 +2162,8 @@ class JavaScriptBackend extends Backend {
|
||||||
jsBuiltinEnum = find(library, 'JsBuiltin');
|
jsBuiltinEnum = find(library, 'JsBuiltin');
|
||||||
} else if (uri == Uris.dart_html) {
|
} else if (uri == Uris.dart_html) {
|
||||||
htmlLibraryIsLoaded = true;
|
htmlLibraryIsLoaded = true;
|
||||||
|
} else if (uri == PACKAGE_LOOKUP_MAP) {
|
||||||
|
lookupMapAnalysis.initRuntimeClass(find(library, 'LookupMap'));
|
||||||
}
|
}
|
||||||
annotations.onLibraryScanned(library);
|
annotations.onLibraryScanned(library);
|
||||||
});
|
});
|
||||||
|
@ -2569,7 +2600,8 @@ class JavaScriptBackend extends Backend {
|
||||||
registerCompileTimeConstant(
|
registerCompileTimeConstant(
|
||||||
dependency.constant,
|
dependency.constant,
|
||||||
new CodegenRegistry(compiler,
|
new CodegenRegistry(compiler,
|
||||||
dependency.annotatedElement.analyzableElement.treeElements));
|
dependency.annotatedElement.analyzableElement.treeElements),
|
||||||
|
addForEmission: false);
|
||||||
}
|
}
|
||||||
metadataConstants.clear();
|
metadataConstants.clear();
|
||||||
}
|
}
|
||||||
|
@ -2577,6 +2609,8 @@ class JavaScriptBackend extends Backend {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onQueueClosed() => lookupMapAnalysis.onQueueClosed();
|
||||||
|
|
||||||
void onElementResolved(Element element, TreeElements elements) {
|
void onElementResolved(Element element, TreeElements elements) {
|
||||||
if ((element.isFunction || element.isGenerativeConstructor) &&
|
if ((element.isFunction || element.isGenerativeConstructor) &&
|
||||||
annotations.noInline(element)) {
|
annotations.noInline(element)) {
|
||||||
|
|
|
@ -169,7 +169,6 @@ class CustomElementsAnalysisJoin {
|
||||||
ConstantValue constant = makeTypeConstant(classElement);
|
ConstantValue constant = makeTypeConstant(classElement);
|
||||||
backend.registerCompileTimeConstant(
|
backend.registerCompileTimeConstant(
|
||||||
constant, compiler.globalDependencies);
|
constant, compiler.globalDependencies);
|
||||||
backend.constants.addCompileTimeConstantForEmission(constant);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activeClasses.addAll(newActiveClasses);
|
activeClasses.addAll(newActiveClasses);
|
||||||
|
|
|
@ -88,6 +88,7 @@ import '../world.dart' show
|
||||||
import 'codegen/task.dart';
|
import 'codegen/task.dart';
|
||||||
import 'constant_system_javascript.dart';
|
import 'constant_system_javascript.dart';
|
||||||
import 'patch_resolver.dart';
|
import 'patch_resolver.dart';
|
||||||
|
import 'lookup_map_analysis.dart' show LookupMapAnalysis;
|
||||||
|
|
||||||
part 'backend.dart';
|
part 'backend.dart';
|
||||||
part 'checked_mode_helpers.dart';
|
part 'checked_mode_helpers.dart';
|
||||||
|
|
335
pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart
Normal file
335
pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart
Normal file
|
@ -0,0 +1,335 @@
|
||||||
|
// Copyright (c) 2015, 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.
|
||||||
|
|
||||||
|
/// Analysis to determine how to generate code for `LookupMap`s.
|
||||||
|
library compiler.src.js_backend.lookup_map_analysis;
|
||||||
|
|
||||||
|
import '../common/registry.dart' show Registry;
|
||||||
|
import '../compiler.dart' show Compiler;
|
||||||
|
import '../constants/values.dart' show
|
||||||
|
ConstantValue,
|
||||||
|
ConstructedConstantValue,
|
||||||
|
ListConstantValue,
|
||||||
|
NullConstantValue,
|
||||||
|
TypeConstantValue;
|
||||||
|
import '../dart_types.dart' show DartType;
|
||||||
|
import '../elements/elements.dart' show Elements, Element, ClassElement,
|
||||||
|
FieldElement, FunctionElement, FunctionSignature;
|
||||||
|
import '../enqueue.dart' show Enqueuer;
|
||||||
|
import 'js_backend.dart' show JavaScriptBackend;
|
||||||
|
import '../dart_types.dart' show DynamicType, InterfaceType;
|
||||||
|
|
||||||
|
/// An analysis and optimization to remove unused entries from a `LookupMap`.
|
||||||
|
///
|
||||||
|
/// `LookupMaps` are defined in `package:lookup_map/lookup_map.dart`. They are
|
||||||
|
/// simple maps that contain constant expressions as keys, and that only support
|
||||||
|
/// the lookup operation.
|
||||||
|
///
|
||||||
|
/// This analysis and optimization will tree-shake the contents of the maps by
|
||||||
|
/// looking at the program and finding which keys are clearly unused. Not all
|
||||||
|
/// constants can be approximated statically, so this optimization is limited to
|
||||||
|
/// the following keys:
|
||||||
|
///
|
||||||
|
/// * Const expressions that can only be created via const constructors. This
|
||||||
|
/// excludes primitives, strings, and any const type that overrides the ==
|
||||||
|
/// operator.
|
||||||
|
///
|
||||||
|
/// * Type literals.
|
||||||
|
///
|
||||||
|
/// Type literals are more complex than const expressions because they can be
|
||||||
|
/// created in multiple ways. We can approximate the possible set of keys if we
|
||||||
|
/// follow these rules:
|
||||||
|
///
|
||||||
|
/// * Include all type-literals used explicitly in the code (excluding
|
||||||
|
/// obviously the uses that can be removed from LookupMaps)
|
||||||
|
///
|
||||||
|
/// * Include every reflectable type-literal if a mirror API is used to create
|
||||||
|
/// types (e.g. ClassMirror.reflectedType).
|
||||||
|
///
|
||||||
|
/// * Include all allocated types if the program contains `e.runtimeType`
|
||||||
|
/// expressions.
|
||||||
|
///
|
||||||
|
/// * Include all generic-type arguments, if the program uses type
|
||||||
|
/// variables in expressions such as `class A<T> { Type get extract => T }`.
|
||||||
|
///
|
||||||
|
// TODO(sigmund): add support for const expressions, currently this
|
||||||
|
// implementation only supports Type literals. To support const expressions we
|
||||||
|
// need to change some of the invariants below (e.g. we can no longer use the
|
||||||
|
// ClassElement of a type to refer to keys we need to discover).
|
||||||
|
// TODO(sigmund): detect uses of mirrors
|
||||||
|
class LookupMapAnalysis {
|
||||||
|
/// Reference to [JavaScriptBackend] to be able to enqueue work when we
|
||||||
|
/// discover that a key in a map is potentially used.
|
||||||
|
final JavaScriptBackend backend;
|
||||||
|
|
||||||
|
/// The resolved [ClassElement] associated with `LookupMap`.
|
||||||
|
ClassElement typeLookupMapClass;
|
||||||
|
|
||||||
|
/// The resolved [FieldElement] for `LookupMap._entries`.
|
||||||
|
FieldElement entriesField;
|
||||||
|
|
||||||
|
/// The resolved [FieldElement] for `LookupMap._key`.
|
||||||
|
FieldElement keyField;
|
||||||
|
|
||||||
|
/// The resolved [FieldElement] for `LookupMap._value`.
|
||||||
|
FieldElement valueField;
|
||||||
|
|
||||||
|
/// Constant instances of `LookupMap` and information about them tracked by
|
||||||
|
/// this analysis.
|
||||||
|
final Map<ConstantValue, _LookupMapInfo> _lookupMaps = {};
|
||||||
|
|
||||||
|
/// Types that we have discovered to be in use in the program.
|
||||||
|
final _inUse = new Set<ClassElement>();
|
||||||
|
|
||||||
|
/// Pending work to do if we discover that a new type is in use. For each type
|
||||||
|
/// that we haven't seen, we record the list of lookup-maps that use such type
|
||||||
|
/// as a key.
|
||||||
|
final _pending = <ClassElement, List<_LookupMapInfo>>{};
|
||||||
|
|
||||||
|
/// Whether the backend is currently processing the codegen queue.
|
||||||
|
// TODO(sigmund): is there a better way to do this. Do we need to plumb the
|
||||||
|
// enqueuer on each callback?
|
||||||
|
bool get _inCodegen => backend.compiler.phase == Compiler.PHASE_COMPILING;
|
||||||
|
|
||||||
|
LookupMapAnalysis(this.backend);
|
||||||
|
|
||||||
|
/// Whether this analysis and optimization is enabled.
|
||||||
|
bool get _isEnabled {
|
||||||
|
// `lookupMap==off` kept here to make it easy to test disabling this feature
|
||||||
|
if (const String.fromEnvironment('lookupMap') == 'off') return false;
|
||||||
|
return typeLookupMapClass != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes this analysis by providing the resolver information of
|
||||||
|
/// `LookupMap`.
|
||||||
|
void initRuntimeClass(ClassElement cls) {
|
||||||
|
cls.computeType(backend.compiler);
|
||||||
|
entriesField = cls.lookupMember('_entries');
|
||||||
|
keyField = cls.lookupMember('_key');
|
||||||
|
valueField = cls.lookupMember('_value');
|
||||||
|
// TODO(sigmund): Maybe inline nested maps make the output code smaller?
|
||||||
|
typeLookupMapClass = cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether [constant] is an instance of a `LookupMap`.
|
||||||
|
bool isLookupMap(ConstantValue constant) =>
|
||||||
|
_isEnabled &&
|
||||||
|
constant is ConstructedConstantValue &&
|
||||||
|
constant.type.asRaw().element.isSubclassOf(typeLookupMapClass);
|
||||||
|
|
||||||
|
/// Registers an instance of a lookup-map with the analysis.
|
||||||
|
void registerLookupMapReference(ConstantValue lookupMap) {
|
||||||
|
if (!_isEnabled || !_inCodegen) return;
|
||||||
|
assert(isLookupMap(lookupMap));
|
||||||
|
_lookupMaps.putIfAbsent(lookupMap,
|
||||||
|
() => new _LookupMapInfo(lookupMap, this).._updateUsed());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records that [type] is used in the program, and updates every map that
|
||||||
|
/// has it as a key.
|
||||||
|
void _addUse(ClassElement type) {
|
||||||
|
if (_inUse.add(type)) {
|
||||||
|
_pending[type]?.forEach((info) => info._markUsed(type));
|
||||||
|
_pending.remove(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback from the enqueuer, invoked when [element] is instantiated.
|
||||||
|
void registerInstantiatedClass(ClassElement element) {
|
||||||
|
if (!_isEnabled || !_inCodegen) return;
|
||||||
|
// TODO(sigmund): only add if .runtimeType is ever used
|
||||||
|
_addUse(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback from the enqueuer, invoked when [type] is instantiated.
|
||||||
|
void registerInstantiatedType(InterfaceType type, Registry registry) {
|
||||||
|
if (!_isEnabled || !_inCodegen) return;
|
||||||
|
// TODO(sigmund): only add if .runtimeType is ever used
|
||||||
|
_addUse(type.element);
|
||||||
|
// TODO(sigmund): only do this when type-argument expressions are used?
|
||||||
|
_addGenerics(type, registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records generic type arguments in [type], in case they are retrieved and
|
||||||
|
/// returned using a type-argument expression.
|
||||||
|
void _addGenerics(InterfaceType type, Registry registry) {
|
||||||
|
if (!type.isGeneric) return;
|
||||||
|
for (var arg in type.typeArguments) {
|
||||||
|
if (arg is InterfaceType) {
|
||||||
|
_addUse(arg.element);
|
||||||
|
// Note: this call was needed to generate correct code for
|
||||||
|
// type_lookup_map/generic_type_test
|
||||||
|
// TODO(sigmund): can we get rid of this?
|
||||||
|
backend.registerInstantiatedConstantType(
|
||||||
|
backend.typeImplementation.rawType, registry);
|
||||||
|
_addGenerics(arg, registry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback from the codegen enqueuer, invoked when a type constant
|
||||||
|
/// corresponding to the [element] is used in the program.
|
||||||
|
void registerTypeConstant(Element element) {
|
||||||
|
if (!_isEnabled || !_inCodegen) return;
|
||||||
|
assert(element.isClass);
|
||||||
|
_addUse(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback from the backend, invoked when reaching the end of the enqueuing
|
||||||
|
/// process, but before emitting the code. At this moment we have discovered
|
||||||
|
/// all types used in the program and we can tree-shake anything that is
|
||||||
|
/// unused.
|
||||||
|
void onQueueClosed() {
|
||||||
|
if (!_isEnabled || !_inCodegen) return;
|
||||||
|
|
||||||
|
_lookupMaps.values.forEach((info) {
|
||||||
|
assert (!info.emitted);
|
||||||
|
info.emitted = true;
|
||||||
|
info._prepareForEmission();
|
||||||
|
});
|
||||||
|
|
||||||
|
// When --verbose is passed, we show the total number and set of keys that
|
||||||
|
// were tree-shaken from lookup maps.
|
||||||
|
Compiler compiler = backend.compiler;
|
||||||
|
if (compiler.verbose) {
|
||||||
|
var sb = new StringBuffer();
|
||||||
|
int count = 0;
|
||||||
|
for (var info in _lookupMaps.values) {
|
||||||
|
for (var key in info.unusedEntries.keys) {
|
||||||
|
if (count != 0) sb.write(',');
|
||||||
|
sb.write(key.unparse());
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compiler.log(count == 0
|
||||||
|
? 'lookup-map: nothing was tree-shaken'
|
||||||
|
: 'lookup-map: found $count unused keys ($sb)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal information about the entries on a lookup-map.
|
||||||
|
class _LookupMapInfo {
|
||||||
|
/// The original reference to the constant value.
|
||||||
|
///
|
||||||
|
/// This reference will be mutated in place to remove it's entries when the
|
||||||
|
/// map is first seen during codegen, and to restore them (or a subset of
|
||||||
|
/// them) when we have finished discovering which entries are used. This has
|
||||||
|
/// the side-effect that `orignal.getDependencies()` will be empty during
|
||||||
|
/// most of codegen until we are ready to emit the constants. However,
|
||||||
|
/// restoring the entries before emitting code lets us keep the emitter logic
|
||||||
|
/// agnostic of this optimization.
|
||||||
|
final ConstructedConstantValue original;
|
||||||
|
|
||||||
|
/// Reference to the lookup map analysis to be able to refer to data shared
|
||||||
|
/// accross infos.
|
||||||
|
final LookupMapAnalysis analysis;
|
||||||
|
|
||||||
|
/// Whether we have already emitted this constant.
|
||||||
|
bool emitted = false;
|
||||||
|
|
||||||
|
/// Whether the `LookupMap` constant was built using the `LookupMap.pair`
|
||||||
|
/// constructor.
|
||||||
|
bool singlePair;
|
||||||
|
|
||||||
|
/// Entries in the lookup map whose keys have not been seen in the rest of the
|
||||||
|
/// program.
|
||||||
|
Map<ClassElement, ConstantValue> unusedEntries =
|
||||||
|
<ClassElement, ConstantValue>{};
|
||||||
|
|
||||||
|
/// Entries that have been used, and thus will be part of the generated code.
|
||||||
|
Map<ClassElement, ConstantValue> usedEntries =
|
||||||
|
<ClassElement, ConstantValue>{};
|
||||||
|
|
||||||
|
/// Internal helper to memoize the mapping between map class elements and
|
||||||
|
/// their corresponding type constants.
|
||||||
|
Map<ClassElement, TypeConstantValue> _typeConstants =
|
||||||
|
<ClassElement, TypeConstantValue>{};
|
||||||
|
|
||||||
|
/// Creates and initializes the information containing all keys of the
|
||||||
|
/// original map marked as unused.
|
||||||
|
_LookupMapInfo(this.original, this.analysis) {
|
||||||
|
ConstantValue key = original.fields[analysis.keyField];
|
||||||
|
singlePair = !key.isNull;
|
||||||
|
|
||||||
|
if (singlePair) {
|
||||||
|
TypeConstantValue typeKey = key;
|
||||||
|
ClassElement cls = typeKey.representedType.element;
|
||||||
|
_typeConstants[cls] = typeKey;
|
||||||
|
unusedEntries[cls] = original.fields[analysis.valueField];
|
||||||
|
|
||||||
|
// Note: we modify the constant in-place, see comment in [original].
|
||||||
|
original.fields[analysis.keyField] = new NullConstantValue();
|
||||||
|
original.fields[analysis.valueField] = new NullConstantValue();
|
||||||
|
} else {
|
||||||
|
ListConstantValue list = original.fields[analysis.entriesField];
|
||||||
|
List<ConstantValue> keyValuePairs = list.entries;
|
||||||
|
for (int i = 0; i < keyValuePairs.length; i += 2) {
|
||||||
|
TypeConstantValue type = keyValuePairs[i];
|
||||||
|
ClassElement cls = type.representedType.element;
|
||||||
|
if (cls == null || !cls.isClass) {
|
||||||
|
// TODO(sigmund): report an error
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_typeConstants[cls] = type;
|
||||||
|
unusedEntries[cls] = keyValuePairs[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we modify the constant in-place, see comment in [original].
|
||||||
|
original.fields[analysis.entriesField] =
|
||||||
|
new ListConstantValue(list.type, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check every key in unusedEntries and mark it as used if the analysis has
|
||||||
|
/// already discovered them. This is meant to be called once to finalize
|
||||||
|
/// initialization after constructing an instance of this class. Afterwards,
|
||||||
|
/// we call [_markUsed] on each individual key as it gets discovered.
|
||||||
|
void _updateUsed() {
|
||||||
|
// Note: we call toList because `_markUsed` modifies the map.
|
||||||
|
for (ClassElement type in unusedEntries.keys.toList()) {
|
||||||
|
if (analysis._inUse.contains(type)) {
|
||||||
|
_markUsed(type);
|
||||||
|
} else {
|
||||||
|
analysis._pending.putIfAbsent(type, () => []).add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks that [type] is a key that has been seen, and thus, the corresponding
|
||||||
|
/// entry in this map should be considered reachable.
|
||||||
|
void _markUsed(ClassElement type) {
|
||||||
|
assert(!emitted);
|
||||||
|
assert(unusedEntries.containsKey(type));
|
||||||
|
assert(!usedEntries.containsKey(type));
|
||||||
|
ConstantValue constant = unusedEntries.remove(type);
|
||||||
|
usedEntries[type] = constant;
|
||||||
|
analysis.backend.registerCompileTimeConstant(constant,
|
||||||
|
analysis.backend.compiler.globalDependencies,
|
||||||
|
addForEmission: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restores [original] to contain all of the entries marked as possibly used.
|
||||||
|
void _prepareForEmission() {
|
||||||
|
ListConstantValue originalEntries = original.fields[analysis.entriesField];
|
||||||
|
DartType listType = originalEntries.type;
|
||||||
|
List<ConstantValue> keyValuePairs = <ConstantValue>[];
|
||||||
|
usedEntries.forEach((key, value) {
|
||||||
|
keyValuePairs.add(_typeConstants[key]);
|
||||||
|
keyValuePairs.add(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note: we are restoring the entries here, see comment in [original].
|
||||||
|
if (singlePair) {
|
||||||
|
assert (keyValuePairs.length == 0 || keyValuePairs.length == 2);
|
||||||
|
if (keyValuePairs.length == 2) {
|
||||||
|
original.fields[analysis.keyField] = keyValuePairs[0];
|
||||||
|
original.fields[analysis.valueField] = keyValuePairs[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
original.fields[analysis.entriesField] =
|
||||||
|
new ListConstantValue(listType, keyValuePairs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1924,7 +1924,6 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
|
||||||
generateConstant(node.constant, node.sourceInformation);
|
generateConstant(node.constant, node.sourceInformation);
|
||||||
|
|
||||||
registry.registerCompileTimeConstant(node.constant);
|
registry.registerCompileTimeConstant(node.constant);
|
||||||
backend.constants.addCompileTimeConstantForEmission(node.constant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitNot(HNot node) {
|
visitNot(HNot node) {
|
||||||
|
|
|
@ -11,6 +11,8 @@ dependencies:
|
||||||
sdk_library_metadata:
|
sdk_library_metadata:
|
||||||
path: ../../sdk/lib/_internal/sdk_library_metadata
|
path: ../../sdk/lib/_internal/sdk_library_metadata
|
||||||
dart2js_info: ^0.0.2
|
dart2js_info: ^0.0.2
|
||||||
|
lookup_map:
|
||||||
|
path: ../lookup_map
|
||||||
|
|
||||||
|
|
||||||
# Uncomment if running gclient, so you can depend directly on the downloaded
|
# Uncomment if running gclient, so you can depend directly on the downloaded
|
||||||
|
|
6
pkg/lookup_map/AUTHORS
Normal file
6
pkg/lookup_map/AUTHORS
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Below is a list of people and organizations that have contributed
|
||||||
|
# to the project. Names should be added to the list like so:
|
||||||
|
#
|
||||||
|
# Name/Organization <email address>
|
||||||
|
|
||||||
|
Google Inc.
|
4
pkg/lookup_map/CHANGELOG.md
Normal file
4
pkg/lookup_map/CHANGELOG.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.0.1
|
||||||
|
- Initial version of `LookupMap`
|
26
pkg/lookup_map/LICENSE
Normal file
26
pkg/lookup_map/LICENSE
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
Copyright 2015, the Dart project authors. All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
pkg/lookup_map/README.md
Normal file
29
pkg/lookup_map/README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Lookup maps
|
||||||
|
|
||||||
|
This package contains the definition of `LookupMap`: a simple, but very
|
||||||
|
restricted map. The map can only hold constant keys and the only way to use the
|
||||||
|
map is to retrieve values with a key you already have. Expect for lookup, any
|
||||||
|
other operation in `Map` (like forEach, keys, values, length, etc) is not
|
||||||
|
available.
|
||||||
|
|
||||||
|
Constant `LookupMap`s are understood by dart2js and can be tree-shaken
|
||||||
|
internally: if a key is not used elsewhere in the program, its entry can be
|
||||||
|
deleted from the map during compilation without changing the program's behavior.
|
||||||
|
Currently dart2js supports tree-shaking keys that are Type literals, and any
|
||||||
|
const expression that can only be created with a const constructor. This means
|
||||||
|
that primitives, Strings, and constant objects that override the `==` operator
|
||||||
|
cannot be tree-shaken.
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
`LookupMap` is unlikely going to be useful for individual developers writing
|
||||||
|
code by hand. It is mainly intended as a helper utility for frameworks that need
|
||||||
|
to autogenerate data and associate it with a type in the program. For example,
|
||||||
|
this can be used by a dependency injection system to record how to create
|
||||||
|
instances of a given type. A dependency injection framework can store in a
|
||||||
|
`LookupMap` all the information it needs for every injectable type in every
|
||||||
|
library and package. When compiling a specific application, dart2js can
|
||||||
|
tree-shake the data of types that are not used by the application. Similarly,
|
||||||
|
this can also be used by serialization/deserialization packages that can store
|
||||||
|
in a `LookupMap` the deserialization logic for a given type.
|
93
pkg/lookup_map/lib/lookup_map.dart
Normal file
93
pkg/lookup_map/lib/lookup_map.dart
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright (c) 2015, 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.
|
||||||
|
|
||||||
|
/// Defines [LookupMap], a simple map that can be optimized by dart2js.
|
||||||
|
library lookup_map;
|
||||||
|
|
||||||
|
/// [LookupMap] is a simple, but very restricted map. The map can only hold
|
||||||
|
/// constant keys and the only way to use the map is to retrieve values with a
|
||||||
|
/// key you already have. Expect for lookup, any other operation in [Map] (like
|
||||||
|
/// forEach, keys, values, length, etc) is not available.
|
||||||
|
///
|
||||||
|
/// Constant [LookupMap]s are understood by dart2js and can be tree-shaken
|
||||||
|
/// internally: if a key is not used elsewhere in the program, its entry can be
|
||||||
|
/// deleted from the map during compilation without changing the program's
|
||||||
|
/// behavior. Currently dart2js supports tree-shaking keys that are `Type`
|
||||||
|
/// literals, and any const expression that can only be created with a const
|
||||||
|
/// constructor. This means that primitives, Strings, and constant objects that
|
||||||
|
/// override the `==` operator cannot be tree-shaken.
|
||||||
|
///
|
||||||
|
/// Note: [LookupMap] is unlikely going to be useful for individual developers
|
||||||
|
/// writing code by hand. It is mainly intended as a helper utility for
|
||||||
|
/// frameworks that need to autogenerate data and associate it with a type in
|
||||||
|
/// the program. For example, this can be used by a dependency injection system
|
||||||
|
/// to record how to create instances of a given type. A dependency injection
|
||||||
|
/// framework can store in a [LookupMap] all the information it needs for every
|
||||||
|
/// injectable type in every library and package. When compiling a specific
|
||||||
|
/// application, dart2js can tree-shake the data of types that are not used by
|
||||||
|
/// the application. Similarly, this can also be used by
|
||||||
|
/// serialization/deserialization packages that can store in a [LookupMap] the
|
||||||
|
/// deserialization logic for a given type.
|
||||||
|
class LookupMap<K, V> {
|
||||||
|
/// The key for [LookupMap]s with a single key/value pair.
|
||||||
|
final K _key;
|
||||||
|
|
||||||
|
/// The value for [LookupMap]s with a single key/value pair.
|
||||||
|
final V _value;
|
||||||
|
|
||||||
|
/// List of alternating key-value pairs in the map.
|
||||||
|
final List _entries;
|
||||||
|
|
||||||
|
/// Other maps to which this map delegates lookup operations if the key is not
|
||||||
|
/// found on [entries]. See [LookupMap]'s constructor for details.
|
||||||
|
final List<LookupMap<K, V>> _nestedMaps;
|
||||||
|
|
||||||
|
/// Creates a lookup-map given a list of key-value pair [entries], and
|
||||||
|
/// optionally additional entries from other [LookupMap]s.
|
||||||
|
///
|
||||||
|
/// When doing a lookup, if the key is not found on [entries]. The lookup will
|
||||||
|
/// be performed in reverse order of the list of [nestedMaps], so a later
|
||||||
|
/// entry for a key shadows previous entries. For example, in:
|
||||||
|
///
|
||||||
|
/// const map = const LookupMap(const [A, 1],
|
||||||
|
/// const [const LookupMap(const [A, 2, B, 4]),
|
||||||
|
/// const LookupMap(const [A, 3, B, 5]));
|
||||||
|
///
|
||||||
|
/// `map[A]` returns `1` and `map[B]` returns `5`.
|
||||||
|
///
|
||||||
|
/// Note: in the future we expect to change [entries] to be a const map
|
||||||
|
/// instead of a list of key-value pairs.
|
||||||
|
// TODO(sigmund): make entries a map once we fix TypeImpl.== (issue #17207).
|
||||||
|
const LookupMap(List entries, [List<LookupMap<K, V>> nestedMaps = const []])
|
||||||
|
: _key = null, _value = null, _entries = entries, _nestedMaps = nestedMaps;
|
||||||
|
|
||||||
|
/// Creates a lookup map with a single key-value pair.
|
||||||
|
const LookupMap.pair(K key, V value)
|
||||||
|
: _key = key, _value = value, _entries = const [], _nestedMaps = const [];
|
||||||
|
|
||||||
|
/// Return the data corresponding to [key].
|
||||||
|
V operator[](K key) {
|
||||||
|
var map = _flatMap[this];
|
||||||
|
if (map == null) {
|
||||||
|
map = {};
|
||||||
|
_addEntriesTo(map);
|
||||||
|
_flatMap[this] = map;
|
||||||
|
}
|
||||||
|
return map[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add to [map] entries from [nestedMaps] and from [entries] according to the
|
||||||
|
/// precedense order described in [nestedMaps].
|
||||||
|
_addEntriesTo(Map map) {
|
||||||
|
_nestedMaps.forEach((m) => m._addEntriesTo(map));
|
||||||
|
for (var i = 0; i < _entries.length; i += 2) {
|
||||||
|
map[_entries[i]] = _entries[i + 1];
|
||||||
|
}
|
||||||
|
if (_key != null) map[_key] = _value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An expando that stores a flatten version of a [LookupMap], this is
|
||||||
|
/// computed and stored the first time the map is accessed.
|
||||||
|
final _flatMap = new Expando('_flat_map');
|
3
pkg/lookup_map/pubspec.yaml
Normal file
3
pkg/lookup_map/pubspec.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name: lookup_map
|
||||||
|
description: a lookup-only map that can be tree-shaken by dart2js
|
||||||
|
version: 0.0.1
|
261
tests/compiler/dart2js/lookup_map_test.dart
Normal file
261
tests/compiler/dart2js/lookup_map_test.dart
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
// Copyright (c) 2015, 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 tests.dart2js.lookup_map_test;
|
||||||
|
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'compiler_helper.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
test('live entries are kept', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
]);
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-A"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('live entries are kept - single-pair', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
const map = const LookupMap.pair(A, "the-text-for-A");
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-A"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unused entries are removed', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
]);
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unused entries are removed - nested maps', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const map = const LookupMap(const [], const [
|
||||||
|
const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unused entries are removed - single-pair', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const map = const LookupMap.pair(A, "the-text-for-A");
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unused entries are removed - nested single-pair', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const map = const LookupMap(const [], const [
|
||||||
|
const LookupMap.pair(A, "the-text-for-A"),
|
||||||
|
const LookupMap.pair(B, "the-text-for-B"),
|
||||||
|
]);
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('works if entries are declared separate from map', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const entries = const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
];
|
||||||
|
const map = const LookupMap(entries);
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('escaping entries disable tree-shaking', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const entries = const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
];
|
||||||
|
const map = const LookupMap(entries);
|
||||||
|
main() {
|
||||||
|
entries.forEach(print);
|
||||||
|
print(map[A]);
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-B"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses include recursively reachable data', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
class C{}
|
||||||
|
class D{}
|
||||||
|
class E{}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, const ["the-text-for-A", B],
|
||||||
|
B, const ["the-text-for-B", C],
|
||||||
|
C, const ["the-text-for-C"],
|
||||||
|
D, const ["the-text-for-D", E],
|
||||||
|
E, const ["the-text-for-E"],
|
||||||
|
]);
|
||||||
|
main() => print(map[map[A][1]]);
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-A"));
|
||||||
|
expect(generated, contains("the-text-for-B"));
|
||||||
|
expect(generated, contains("the-text-for-C"));
|
||||||
|
expect(generated, isNot(contains("the-text-for-D")));
|
||||||
|
expect(generated, isNot(contains("the-text-for-E")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses are found through newly discovered code', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{ A(B x);}
|
||||||
|
class B{}
|
||||||
|
class C{}
|
||||||
|
class D{}
|
||||||
|
class E{}
|
||||||
|
createA() => new A(map[B][1]());
|
||||||
|
createB() => new B();
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, const ["the-text-for-A", createA],
|
||||||
|
B, const ["the-text-for-B", createB],
|
||||||
|
C, const ["the-text-for-C"],
|
||||||
|
]);
|
||||||
|
main() => print(map[A][1]());
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-A"));
|
||||||
|
expect(generated, contains("the-text-for-B"));
|
||||||
|
expect(generated, isNot(contains("the-text-for-C")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generic type allocations are considered used', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class M<T>{ get type => T; }
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
]);
|
||||||
|
main() => print(map[new M<A>().type]);
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-A"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generics in type signatures are ignored', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
class M<T>{ get type => T; }
|
||||||
|
_factory(M<B> t) => t;
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, const ["the-text-for-A", _factory],
|
||||||
|
B, "the-text-for-B",
|
||||||
|
]);
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('metadata is ignored', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{ const A(); }
|
||||||
|
|
||||||
|
@A()
|
||||||
|
class M {}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
]);
|
||||||
|
main() => print(map[M]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-A")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shared constants used in metadata are ignored', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
const annot = const B(foo: A);
|
||||||
|
|
||||||
|
@B(foo: annot)
|
||||||
|
class A{ const A(); }
|
||||||
|
class B{ final Type foo; const B({this.foo}); }
|
||||||
|
|
||||||
|
class M {}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, const ["the-text-for-A", annot]
|
||||||
|
]);
|
||||||
|
main() => print(map[M]);
|
||||||
|
""");
|
||||||
|
expect(generated, isNot(contains("the-text-for-A")));
|
||||||
|
});
|
||||||
|
|
||||||
|
// regression test for a failure when looking up `dynamic` in a generic.
|
||||||
|
test('do not choke on dynamic types', () async {
|
||||||
|
await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class M<T>{ get type => T; }
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
]);
|
||||||
|
main() => print(map[new M<dynamic>().type]);
|
||||||
|
""");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('support subclassing LookupMap', () async {
|
||||||
|
String generated = await compileAll(r"""
|
||||||
|
import 'package:lookup_map/lookup_map.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
class S extends LookupMap {
|
||||||
|
const S(list) : super(list);
|
||||||
|
}
|
||||||
|
const map = const S(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
]);
|
||||||
|
|
||||||
|
main() => print(map[A]);
|
||||||
|
""");
|
||||||
|
expect(generated, contains("the-text-for-A"));
|
||||||
|
expect(generated, isNot(contains("the-text-for-B")));
|
||||||
|
});
|
||||||
|
}
|
|
@ -134,6 +134,8 @@ class MockCompiler extends Compiler {
|
||||||
}
|
}
|
||||||
registerSource(Uris.dart_async,
|
registerSource(Uris.dart_async,
|
||||||
buildLibrarySource(asyncLibrarySource));
|
buildLibrarySource(asyncLibrarySource));
|
||||||
|
registerSource(JavaScriptBackend.PACKAGE_LOOKUP_MAP,
|
||||||
|
buildLibrarySource(DEFAULT_LOOKUP_MAP_LIBRARY));
|
||||||
}
|
}
|
||||||
|
|
||||||
String get patchVersion {
|
String get patchVersion {
|
||||||
|
|
|
@ -398,3 +398,19 @@ const Map<String, String> DEFAULT_MIRRORS_LIBRARY = const <String, String>{
|
||||||
'MirrorSystem': 'class MirrorSystem {}',
|
'MirrorSystem': 'class MirrorSystem {}',
|
||||||
'MirrorsUsed': 'class MirrorsUsed {}',
|
'MirrorsUsed': 'class MirrorsUsed {}',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Map<String, String> DEFAULT_LOOKUP_MAP_LIBRARY = const <String, String>{
|
||||||
|
'LookupMap': r'''
|
||||||
|
class LookupMap<T> {
|
||||||
|
final _key;
|
||||||
|
final _value;
|
||||||
|
final _entries;
|
||||||
|
final _nestedMaps;
|
||||||
|
|
||||||
|
const LookupMap(this._entries, [this._nestedMaps = const []])
|
||||||
|
: _key = null, _value = null;
|
||||||
|
|
||||||
|
const LookupMap.pair(this._key, this._value)
|
||||||
|
: _entries = const [], _nestedMaps = const [];
|
||||||
|
}''',
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const map = const LookupMap(const [], const [
|
||||||
|
const LookupMap.pair(A, "the-text-for-A"),
|
||||||
|
const LookupMap.pair(B, "the-text-for-B"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[A], "the-text-for-A");
|
||||||
|
}
|
16
tests/compiler/dart2js_extra/lookup_map/dead_entry_test.dart
Normal file
16
tests/compiler/dart2js_extra/lookup_map/dead_entry_test.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
]);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[A], "the-text-for-A");
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
|
||||||
|
class A{ A(B x);}
|
||||||
|
class B{}
|
||||||
|
class C{}
|
||||||
|
class D{}
|
||||||
|
class E{}
|
||||||
|
createA() => new A(map[B][1]());
|
||||||
|
createB() => new B();
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, const ["the-text-for-A", createA],
|
||||||
|
B, const ["the-text-for-B", createB],
|
||||||
|
C, const ["the-text-for-C"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Expect.isTrue(map[A][1]() is A);
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const entries = const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
];
|
||||||
|
const map = const LookupMap(entries );
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[A], 'the-text-for-A');
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
const entries = const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
];
|
||||||
|
const map = const LookupMap(entries);
|
||||||
|
main() {
|
||||||
|
entries.forEach(print);
|
||||||
|
Expect.equals(map[A], 'the-text-for-A');
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
|
||||||
|
class A{}
|
||||||
|
class M<T>{ get type => T; }
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, 'the-text-for-A',
|
||||||
|
]);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[new M<A>().type], 'the-text-for-A');
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
class A{}
|
||||||
|
const map = const LookupMap.pair(A, "the-text-for-A");
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[A], 'the-text-for-A');
|
||||||
|
}
|
13
tests/compiler/dart2js_extra/lookup_map/live_entry_test.dart
Normal file
13
tests/compiler/dart2js_extra/lookup_map/live_entry_test.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
class A{}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
]);
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[A], 'the-text-for-A');
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
class C{}
|
||||||
|
class D{}
|
||||||
|
class E{}
|
||||||
|
const map = const LookupMap(const [
|
||||||
|
A, const ["the-text-for-A", B],
|
||||||
|
B, const ["the-text-for-B", C],
|
||||||
|
C, const ["the-text-for-C"],
|
||||||
|
D, const ["the-text-for-D", E],
|
||||||
|
E, const ["the-text-for-E"],
|
||||||
|
]);
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[map[A][1]][0], 'the-text-for-B');
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) 2015, 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:lookup_map/lookup_map.dart';
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
class A{}
|
||||||
|
class B{}
|
||||||
|
class S extends LookupMap {
|
||||||
|
const S(list) : super(list);
|
||||||
|
}
|
||||||
|
const map = const S(const [
|
||||||
|
A, "the-text-for-A",
|
||||||
|
B, "the-text-for-B",
|
||||||
|
]);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Expect.equals(map[A], "the-text-for-A");
|
||||||
|
}
|
Loading…
Reference in a new issue