From 2c660aa2f03e6932a1ab26e9ca69168a2191b2f2 Mon Sep 17 00:00:00 2001 From: Sigmund Cherem Date: Wed, 2 Sep 2015 17:51:59 -0700 Subject: [PATCH] dart2js: add initial support for lookup-maps R=herhut@google.com, sra@google.com Review URL: https://codereview.chromium.org//1320503003 . --- pkg/compiler/lib/src/common/backend_api.dart | 4 + pkg/compiler/lib/src/common/codegen.dart | 2 +- pkg/compiler/lib/src/compiler.dart | 1 + pkg/compiler/lib/src/js_backend/backend.dart | 46 ++- .../js_backend/custom_elements_analysis.dart | 1 - .../lib/src/js_backend/js_backend.dart | 1 + .../src/js_backend/lookup_map_analysis.dart | 335 ++++++++++++++++++ pkg/compiler/lib/src/ssa/codegen.dart | 1 - pkg/compiler/pubspec.yaml | 2 + pkg/lookup_map/AUTHORS | 6 + pkg/lookup_map/CHANGELOG.md | 4 + pkg/lookup_map/LICENSE | 26 ++ pkg/lookup_map/README.md | 29 ++ pkg/lookup_map/lib/lookup_map.dart | 93 +++++ pkg/lookup_map/pubspec.yaml | 3 + tests/compiler/dart2js/lookup_map_test.dart | 261 ++++++++++++++ tests/compiler/dart2js/mock_compiler.dart | 2 + tests/compiler/dart2js/mock_libraries.dart | 16 + .../dead_entry_single_nested_pairs_test.dart | 16 + .../lookup_map/dead_entry_test.dart | 16 + .../lookup_map/discovered_code_test.dart | 23 ++ .../lookup_map/entries_aside_test.dart | 18 + .../lookup_map/escaping_entries_test.dart | 17 + .../lookup_map/generic_type_test.dart | 16 + .../live_entry_single_pair_test.dart | 11 + .../lookup_map/live_entry_test.dart | 13 + .../lookup_map/reachable_data_test.dart | 22 ++ .../lookup_map/subclass_lookup_map_test.dart | 19 + 28 files changed, 995 insertions(+), 9 deletions(-) create mode 100644 pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart create mode 100644 pkg/lookup_map/AUTHORS create mode 100644 pkg/lookup_map/CHANGELOG.md create mode 100644 pkg/lookup_map/LICENSE create mode 100644 pkg/lookup_map/README.md create mode 100644 pkg/lookup_map/lib/lookup_map.dart create mode 100644 pkg/lookup_map/pubspec.yaml create mode 100644 tests/compiler/dart2js/lookup_map_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/dead_entry_single_nested_pairs_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/dead_entry_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/discovered_code_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/entries_aside_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/escaping_entries_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/generic_type_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/live_entry_single_pair_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/live_entry_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/reachable_data_test.dart create mode 100644 tests/compiler/dart2js_extra/lookup_map/subclass_lookup_map_test.dart diff --git a/pkg/compiler/lib/src/common/backend_api.dart b/pkg/compiler/lib/src/common/backend_api.dart index 63b112e22c1..e715f29ab36 100644 --- a/pkg/compiler/lib/src/common/backend_api.dart +++ b/pkg/compiler/lib/src/common/backend_api.dart @@ -339,6 +339,10 @@ abstract class Backend { 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. // TODO(johnniwinther): Change [TreeElements] to [Registry] or a dependency // node. [elements] is currently unused by the implementation. diff --git a/pkg/compiler/lib/src/common/codegen.dart b/pkg/compiler/lib/src/common/codegen.dart index 538b06d66db..323822b1f47 100644 --- a/pkg/compiler/lib/src/common/codegen.dart +++ b/pkg/compiler/lib/src/common/codegen.dart @@ -112,7 +112,6 @@ class CodegenRegistry extends Registry { void registerCompileTimeConstant(ConstantValue constant) { backend.registerCompileTimeConstant(constant, this); - backend.constants.addCompileTimeConstantForEmission(constant); } void registerTypeVariableBoundsSubtypeCheck(DartType subtype, @@ -146,6 +145,7 @@ class CodegenRegistry extends Registry { void registerTypeConstant(ClassElement element) { backend.customElementsAnalysis.registerTypeConstant(element, world); + backend.lookupMapAnalysis.registerTypeConstant(element); } void registerStaticInvocation(Element element) { diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart index 1f7670b1ce4..e04c88a3f37 100644 --- a/pkg/compiler/lib/src/compiler.dart +++ b/pkg/compiler/lib/src/compiler.dart @@ -1240,6 +1240,7 @@ abstract class Compiler implements DiagnosticListener { } emptyQueue(world); world.queueIsClosed = true; + backend.onQueueClosed(); assert(compilationFailed || world.checkNoEnqueuedInvokedInstanceMethods()); } diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart index ce801a9b841..f8ba2418a48 100644 --- a/pkg/compiler/lib/src/js_backend/backend.dart +++ b/pkg/compiler/lib/src/js_backend/backend.dart @@ -233,6 +233,8 @@ class JavaScriptBackend extends Backend { new Uri(scheme: 'dart', path: '_js_embedded_names'); static final Uri DART_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 START_ROOT_ISOLATE = 'startRootIsolate'; @@ -608,6 +610,9 @@ class JavaScriptBackend extends Backend { /// constructors for custom elements. CustomElementsAnalysis customElementsAnalysis; + /// Codegen support for tree-shaking entries of `LookupMap`. + LookupMapAnalysis lookupMapAnalysis; + /// Support for classifying `noSuchMethod` implementations. NoSuchMethodRegistry noSuchMethodRegistry; @@ -641,6 +646,7 @@ class JavaScriptBackend extends Backend { compiler, namer, generateSourceMap, useStartupEmitter); typeVariableHandler = new TypeVariableHandler(compiler); customElementsAnalysis = new CustomElementsAnalysis(this); + lookupMapAnalysis = new LookupMapAnalysis(this); noSuchMethodRegistry = new NoSuchMethodRegistry(this); constantCompilerTask = new JavaScriptConstantTask(compiler); 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); - 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, @@ -971,6 +989,11 @@ class JavaScriptBackend extends Backend { } else if (constant.isType) { enqueueInResolution(getCreateRuntimeType(), registry); 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) { assert(registry.isForResolution); ConstantValue constant = constants.getConstantValueForMetadata(metadata); - registerCompileTimeConstant(constant, registry); + registerCompileTimeConstant(constant, registry, addForEmission: false); metadataConstants.add(new Dependency(constant, annotatedElement)); } @@ -1129,6 +1152,13 @@ class JavaScriptBackend extends Backend { } customElementsAnalysis.registerInstantiatedClass(cls, enqueuer); + if (!enqueuer.isResolutionQueue) { + lookupMapAnalysis.registerInstantiatedClass(cls); + } + } + + void registerInstantiatedType(InterfaceType type, Registry registry) { + lookupMapAnalysis.registerInstantiatedType(type, registry); } void registerUseInterceptor(Enqueuer enqueuer) { @@ -1438,7 +1468,6 @@ class JavaScriptBackend extends Backend { constants.getConstantValueForVariable(element); if (initialValue != null) { registerCompileTimeConstant(initialValue, work.registry); - constants.addCompileTimeConstantForEmission(initialValue); // We don't need to generate code for static or top-level // variables. For instance variables, we may need to generate // the checked setter. @@ -2133,6 +2162,8 @@ class JavaScriptBackend extends Backend { jsBuiltinEnum = find(library, 'JsBuiltin'); } else if (uri == Uris.dart_html) { htmlLibraryIsLoaded = true; + } else if (uri == PACKAGE_LOOKUP_MAP) { + lookupMapAnalysis.initRuntimeClass(find(library, 'LookupMap')); } annotations.onLibraryScanned(library); }); @@ -2569,7 +2600,8 @@ class JavaScriptBackend extends Backend { registerCompileTimeConstant( dependency.constant, new CodegenRegistry(compiler, - dependency.annotatedElement.analyzableElement.treeElements)); + dependency.annotatedElement.analyzableElement.treeElements), + addForEmission: false); } metadataConstants.clear(); } @@ -2577,6 +2609,8 @@ class JavaScriptBackend extends Backend { return true; } + void onQueueClosed() => lookupMapAnalysis.onQueueClosed(); + void onElementResolved(Element element, TreeElements elements) { if ((element.isFunction || element.isGenerativeConstructor) && annotations.noInline(element)) { diff --git a/pkg/compiler/lib/src/js_backend/custom_elements_analysis.dart b/pkg/compiler/lib/src/js_backend/custom_elements_analysis.dart index a13db64f16f..1cd0c4329e5 100644 --- a/pkg/compiler/lib/src/js_backend/custom_elements_analysis.dart +++ b/pkg/compiler/lib/src/js_backend/custom_elements_analysis.dart @@ -169,7 +169,6 @@ class CustomElementsAnalysisJoin { ConstantValue constant = makeTypeConstant(classElement); backend.registerCompileTimeConstant( constant, compiler.globalDependencies); - backend.constants.addCompileTimeConstantForEmission(constant); } } activeClasses.addAll(newActiveClasses); diff --git a/pkg/compiler/lib/src/js_backend/js_backend.dart b/pkg/compiler/lib/src/js_backend/js_backend.dart index 9f2e82e5d96..af8df5127b1 100644 --- a/pkg/compiler/lib/src/js_backend/js_backend.dart +++ b/pkg/compiler/lib/src/js_backend/js_backend.dart @@ -88,6 +88,7 @@ import '../world.dart' show import 'codegen/task.dart'; import 'constant_system_javascript.dart'; import 'patch_resolver.dart'; +import 'lookup_map_analysis.dart' show LookupMapAnalysis; part 'backend.dart'; part 'checked_mode_helpers.dart'; diff --git a/pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart b/pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart new file mode 100644 index 00000000000..9d44af11ec3 --- /dev/null +++ b/pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart @@ -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 { 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 _lookupMaps = {}; + + /// Types that we have discovered to be in use in the program. + final _inUse = new Set(); + + /// 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 = >{}; + + /// 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 unusedEntries = + {}; + + /// Entries that have been used, and thus will be part of the generated code. + Map usedEntries = + {}; + + /// Internal helper to memoize the mapping between map class elements and + /// their corresponding type constants. + Map _typeConstants = + {}; + + /// 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 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 keyValuePairs = []; + 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); + } + } +} diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart index 998ed3659b9..6a2002c1143 100644 --- a/pkg/compiler/lib/src/ssa/codegen.dart +++ b/pkg/compiler/lib/src/ssa/codegen.dart @@ -1924,7 +1924,6 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor { generateConstant(node.constant, node.sourceInformation); registry.registerCompileTimeConstant(node.constant); - backend.constants.addCompileTimeConstantForEmission(node.constant); } visitNot(HNot node) { diff --git a/pkg/compiler/pubspec.yaml b/pkg/compiler/pubspec.yaml index ad922d26923..fe774b25f4b 100644 --- a/pkg/compiler/pubspec.yaml +++ b/pkg/compiler/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: sdk_library_metadata: path: ../../sdk/lib/_internal/sdk_library_metadata dart2js_info: ^0.0.2 + lookup_map: + path: ../lookup_map # Uncomment if running gclient, so you can depend directly on the downloaded diff --git a/pkg/lookup_map/AUTHORS b/pkg/lookup_map/AUTHORS new file mode 100644 index 00000000000..e8063a8cd6e --- /dev/null +++ b/pkg/lookup_map/AUTHORS @@ -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 + +Google Inc. diff --git a/pkg/lookup_map/CHANGELOG.md b/pkg/lookup_map/CHANGELOG.md new file mode 100644 index 00000000000..cbe7954a546 --- /dev/null +++ b/pkg/lookup_map/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +## 0.0.1 +- Initial version of `LookupMap` diff --git a/pkg/lookup_map/LICENSE b/pkg/lookup_map/LICENSE new file mode 100644 index 00000000000..de31e1a0a48 --- /dev/null +++ b/pkg/lookup_map/LICENSE @@ -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. diff --git a/pkg/lookup_map/README.md b/pkg/lookup_map/README.md new file mode 100644 index 00000000000..73f5a81519a --- /dev/null +++ b/pkg/lookup_map/README.md @@ -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. diff --git a/pkg/lookup_map/lib/lookup_map.dart b/pkg/lookup_map/lib/lookup_map.dart new file mode 100644 index 00000000000..609f854288d --- /dev/null +++ b/pkg/lookup_map/lib/lookup_map.dart @@ -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 { + /// 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> _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> 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'); diff --git a/pkg/lookup_map/pubspec.yaml b/pkg/lookup_map/pubspec.yaml new file mode 100644 index 00000000000..fdc8946522c --- /dev/null +++ b/pkg/lookup_map/pubspec.yaml @@ -0,0 +1,3 @@ +name: lookup_map +description: a lookup-only map that can be tree-shaken by dart2js +version: 0.0.1 diff --git a/tests/compiler/dart2js/lookup_map_test.dart b/tests/compiler/dart2js/lookup_map_test.dart new file mode 100644 index 00000000000..d73bc75ea7d --- /dev/null +++ b/tests/compiler/dart2js/lookup_map_test.dart @@ -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{ get type => T; } + const map = const LookupMap(const [ + A, "the-text-for-A", + ]); + main() => print(map[new M().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{ get type => T; } + _factory(M 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{ get type => T; } + const map = const LookupMap(const [ + A, "the-text-for-A", + ]); + main() => print(map[new M().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"))); + }); +} diff --git a/tests/compiler/dart2js/mock_compiler.dart b/tests/compiler/dart2js/mock_compiler.dart index 2a9a39fa6a0..e18571237b2 100644 --- a/tests/compiler/dart2js/mock_compiler.dart +++ b/tests/compiler/dart2js/mock_compiler.dart @@ -134,6 +134,8 @@ class MockCompiler extends Compiler { } registerSource(Uris.dart_async, buildLibrarySource(asyncLibrarySource)); + registerSource(JavaScriptBackend.PACKAGE_LOOKUP_MAP, + buildLibrarySource(DEFAULT_LOOKUP_MAP_LIBRARY)); } String get patchVersion { diff --git a/tests/compiler/dart2js/mock_libraries.dart b/tests/compiler/dart2js/mock_libraries.dart index d70afdb1fb5..2031cf035b4 100644 --- a/tests/compiler/dart2js/mock_libraries.dart +++ b/tests/compiler/dart2js/mock_libraries.dart @@ -398,3 +398,19 @@ const Map DEFAULT_MIRRORS_LIBRARY = const { 'MirrorSystem': 'class MirrorSystem {}', 'MirrorsUsed': 'class MirrorsUsed {}', }; + +const Map DEFAULT_LOOKUP_MAP_LIBRARY = const { + 'LookupMap': r''' + class LookupMap { + 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 []; + }''', +}; diff --git a/tests/compiler/dart2js_extra/lookup_map/dead_entry_single_nested_pairs_test.dart b/tests/compiler/dart2js_extra/lookup_map/dead_entry_single_nested_pairs_test.dart new file mode 100644 index 00000000000..79446f22c89 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/dead_entry_single_nested_pairs_test.dart @@ -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"); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/dead_entry_test.dart b/tests/compiler/dart2js_extra/lookup_map/dead_entry_test.dart new file mode 100644 index 00000000000..c5fb956f9f5 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/dead_entry_test.dart @@ -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"); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/discovered_code_test.dart b/tests/compiler/dart2js_extra/lookup_map/discovered_code_test.dart new file mode 100644 index 00000000000..a20af731860 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/discovered_code_test.dart @@ -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); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/entries_aside_test.dart b/tests/compiler/dart2js_extra/lookup_map/entries_aside_test.dart new file mode 100644 index 00000000000..6f7241892fc --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/entries_aside_test.dart @@ -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'); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/escaping_entries_test.dart b/tests/compiler/dart2js_extra/lookup_map/escaping_entries_test.dart new file mode 100644 index 00000000000..30fa3d30562 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/escaping_entries_test.dart @@ -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'); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/generic_type_test.dart b/tests/compiler/dart2js_extra/lookup_map/generic_type_test.dart new file mode 100644 index 00000000000..1a2fd6e3e03 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/generic_type_test.dart @@ -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{ get type => T; } +const map = const LookupMap(const [ + A, 'the-text-for-A', +]); + +main() { + Expect.equals(map[new M().type], 'the-text-for-A'); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/live_entry_single_pair_test.dart b/tests/compiler/dart2js_extra/lookup_map/live_entry_single_pair_test.dart new file mode 100644 index 00000000000..2954e233fed --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/live_entry_single_pair_test.dart @@ -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'); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/live_entry_test.dart b/tests/compiler/dart2js_extra/lookup_map/live_entry_test.dart new file mode 100644 index 00000000000..aa835a8562b --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/live_entry_test.dart @@ -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'); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/reachable_data_test.dart b/tests/compiler/dart2js_extra/lookup_map/reachable_data_test.dart new file mode 100644 index 00000000000..8e71ea7ec18 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/reachable_data_test.dart @@ -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'); +} diff --git a/tests/compiler/dart2js_extra/lookup_map/subclass_lookup_map_test.dart b/tests/compiler/dart2js_extra/lookup_map/subclass_lookup_map_test.dart new file mode 100644 index 00000000000..ebf74160243 --- /dev/null +++ b/tests/compiler/dart2js_extra/lookup_map/subclass_lookup_map_test.dart @@ -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"); +}