From 79be3d60cbfef6cf07f7e00a86a8230649f61e11 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 19 Jun 2014 09:52:35 -0700 Subject: [PATCH 1/5] chore(transformer): Use dartSdkDirectory from code_transformers for SDK location --- lib/transformer.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/transformer.dart b/lib/transformer.dart index e7bb1ed..0e74ccd 100644 --- a/lib/transformer.dart +++ b/lib/transformer.dart @@ -77,10 +77,7 @@ TransformOptions _parseSettings(Map args) { var injectedTypes = _readStringListValue(args, 'injected_types'); var sdkDir = _readStringValue(args, 'dart_sdk', required: false); - if (sdkDir == null) { - // Assume the Pub executable is always coming from the SDK. - sdkDir = path.dirname(path.dirname(Platform.executable)); - } + if (sdkDir == null) sdkDir = dartSdkDirectory; return new TransformOptions( injectableAnnotations: annotations, From 57523ea0a7732b4a54850790c341525b3b28d569 Mon Sep 17 00:00:00 2001 From: Anting Shen Date: Fri, 18 Jul 2014 10:18:33 -0700 Subject: [PATCH 2/5] test(dart2js): Added preamble to dart2js test (required by dart 1.6+) --- run-tests.sh | 9 +- test_assets/d8.js | 283 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 test_assets/d8.js diff --git a/run-tests.sh b/run-tests.sh index 3341ae8..203f5d0 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -14,8 +14,13 @@ echo "run dart2js on tests" mkdir -p out dart2js --minify -c test/main.dart -o out/main.dart.js -echo "run tests in node" -node out/main.dart.js +# attach a preamble file to dart2js output to emulate browser +# so node doesn't complain about lack of browser objects +cp test_assets/d8.js out/main.js +cat out/main.dart.js >> out/main.js + +echo "Running compiled tests in node..." +node out/main.js echo "run transformer tests" pub build --mode=debug test diff --git a/test_assets/d8.js b/test_assets/d8.js new file mode 100644 index 0000000..aed0161 --- /dev/null +++ b/test_assets/d8.js @@ -0,0 +1,283 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Javascript preamble, that lets the output of dart2js run on V8's d8 shell. + +// Node wraps files and provides them with a different `this`. The global +// `this` can be accessed through `global`. + +var self = this; +if (typeof global != "undefined") self = global; // Node.js. + +(function(self) { + // Using strict mode to avoid accidentally defining global variables. + "use strict"; // Should be first statement of this function. + + // Location (Uri.base) + + var workingDirectory; + // TODO(sgjesse): This does not work on Windows. + if (typeof os == "object" && "system" in os) { + // V8. + workingDirectory = os.system("pwd"); + var length = workingDirectory.length; + if (workingDirectory[length - 1] == '\n') { + workingDirectory = workingDirectory.substring(0, length - 1); + } + } else if (typeof process != "undefined" && + typeof process.cwd == "function") { + // Node.js. + workingDirectory = process.cwd(); + } + self.location = { href: "file://" + workingDirectory + "/" }; + + // Event loop. + + // Task queue as cyclic list queue. + var taskQueue = new Array(8); // Length is power of 2. + var head = 0; + var tail = 0; + var mask = taskQueue.length - 1; + function addTask(elem) { + taskQueue[head] = elem; + head = (head + 1) & mask; + if (head == tail) _growTaskQueue(); + } + function removeTask() { + if (head == tail) return; + var result = taskQueue[tail]; + taskQueue[tail] = undefined; + tail = (tail + 1) & mask; + return result; + } + function _growTaskQueue() { + // head == tail. + var length = taskQueue.length; + var split = head; + taskQueue.length = length * 2; + if (split * 2 < length) { // split < length / 2 + for (var i = 0; i < split; i++) { + taskQueue[length + i] = taskQueue[i]; + taskQueue[i] = undefined; + } + head += length; + } else { + for (var i = split; i < length; i++) { + taskQueue[length + i] = taskQueue[i]; + taskQueue[i] = undefined; + } + tail += length; + } + mask = taskQueue.length - 1; + } + + // Mapping from timer id to timer function. + // The timer id is written on the function as .$timerId. + // That field is cleared when the timer is cancelled, but it is not returned + // from the queue until its time comes. + var timerIds = {}; + var timerIdCounter = 1; // Counter used to assing ids. + + // Zero-timer queue as simple array queue using push/shift. + var zeroTimerQueue = []; + + function addTimer(f, ms) { + var id = timerIdCounter++; + f.$timerId = id; + timerIds[id] = f; + if (ms == 0) { + zeroTimerQueue.push(f); + } else { + addDelayedTimer(f, ms); + } + return id; + } + + function nextZeroTimer() { + while (zeroTimerQueue.length > 0) { + var action = zeroTimerQueue.shift(); + if (action.$timerId !== undefined) return action; + } + } + + function nextEvent() { + var action = removeTask(); + if (action) { + return action; + } + do { + action = nextZeroTimer(); + if (action) break; + var nextList = nextDelayedTimerQueue(); + if (!nextList) { + return; + } + var newTime = nextList.shift(); + advanceTimeTo(newTime); + zeroTimerQueue = nextList; + } while (true) + var id = action.$timerId; + clearTimerId(action, id); + return action; + } + + // Mocking time. + var timeOffset = 0; + var now = function() { + // Install the mock Date object only once. + // Following calls to "now" will just use the new (mocked) Date.now + // method directly. + installMockDate(); + now = Date.now; + return Date.now(); + }; + var originalDate = Date; + var originalNow = originalDate.now; + function advanceTimeTo(time) { + timeOffset = time - originalNow(); + } + function installMockDate() { + var NewDate = function Date(Y, M, D, h, m, s, ms) { + if (this instanceof Date) { + // Assume a construct call. + switch (arguments.length) { + case 0: return new originalDate(originalNow() + timeOffset); + case 1: return new originalDate(Y); + case 2: return new originalDate(Y, M); + case 3: return new originalDate(Y, M, D); + case 4: return new originalDate(Y, M, D, h); + case 5: return new originalDate(Y, M, D, h, m); + case 6: return new originalDate(Y, M, D, h, m, s); + default: return new originalDate(Y, M, D, h, m, s, ms); + } + } + return new originalDate(originalNow() + timeOffset).toString(); + }; + NewDate.UTC = originalDate.UTC; + NewDate.parse = originalDate.parse; + NewDate.now = function now() { return originalNow() + timeOffset; }; + NewDate.prototype = originalDate.prototype; + originalDate.prototype.constructor = NewDate; + Date = NewDate; + } + + // Heap priority queue with key index. + // Each entry is list of [timeout, callback1 ... callbackn]. + var timerHeap = []; + var timerIndex = {}; + function addDelayedTimer(f, ms) { + var timeout = now() + ms; + var timerList = timerIndex[timeout]; + if (timerList == null) { + timerList = [timeout, f]; + timerIndex[timeout] = timerList; + var index = timerHeap.length; + timerHeap.length += 1; + bubbleUp(index, timeout, timerList); + } else { + timerList.push(f); + } + } + + function nextDelayedTimerQueue() { + if (timerHeap.length == 0) return null; + var result = timerHeap[0]; + var last = timerHeap.pop(); + if (timerHeap.length > 0) { + bubbleDown(0, last[0], last); + } + return result; + } + + function bubbleUp(index, key, value) { + while (index != 0) { + var parentIndex = (index - 1) >> 1; + var parent = timerHeap[parentIndex]; + var parentKey = parent[0]; + if (key > parentKey) break; + timerHeap[index] = parent; + index = parentIndex; + } + timerHeap[index] = value; + } + + function bubbleDown(index, key, value) { + while (true) { + var leftChildIndex = index * 2 + 1; + if (leftChildIndex >= timerHeap.length) break; + var minChildIndex = leftChildIndex; + var minChild = timerHeap[leftChildIndex]; + var minChildKey = minChild[0]; + var rightChildIndex = leftChildIndex + 1; + if (rightChildIndex < timerHeap.length) { + var rightChild = timerHeap[rightChildIndex]; + var rightKey = rightChild[0]; + if (rightKey < minChildKey) { + minChildIndex = rightChildIndex; + minChild = rightChild; + minChildKey = rightKey; + } + } + if (minChildKey > key) break; + timerHeap[index] = minChild; + index = minChildIndex; + } + timerHeap[index] = value; + } + + function addInterval(f, ms) { + var id = timerIdCounter++; + function repeat() { + // Reactivate with the same id. + repeat.$timerId = id; + timerIds[id] = repeat; + addDelayedTimer(repeat, ms); + f(); + } + repeat.$timerId = id; + timerIds[id] = repeat; + addDelayedTimer(repeat, ms); + return id; + } + + function cancelTimer(id) { + var f = timerIds[id]; + if (f == null) return; + clearTimerId(f, id); + } + + function clearTimerId(f, id) { + f.$timerId = undefined; + delete timerIds[id]; + } + + function eventLoop(action) { + while (action) { + try { + action(); + } catch (e) { + if (typeof onerror == "function") { + onerror(e, null, -1); + } else { + throw e; + } + } + action = nextEvent(); + } + } + + // Global properties. "self" refers to the global object, so adding a + // property to "self" defines a global variable. + self.dartMainRunner = function(main, args) { + // Initialize. + var action = function() { main(args); } + eventLoop(action); + }; + self.setTimeout = addTimer; + self.clearTimeout = cancelTimer; + self.setInterval = addInterval; + self.clearInterval = cancelTimer; + self.scheduleImmediate = addTask; + self.self = self; +})(self); From 10e471b0ea2e4392d436cfa555f6c613fc4a7043 Mon Sep 17 00:00:00 2001 From: Anting Shen Date: Wed, 25 Jun 2014 16:02:46 -0700 Subject: [PATCH 3/5] refactor: Simpler, Faster injector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE - Calls to StaticInjector and DynamicInjector should be replaced with ModuleInjector - There are no longer StaticInjectors and DynamicInjectors. They have been replaced by a new ModuleInjector class that acts as both types of injectors. There is also a faster NodeInjector class for specific use cases where many injector features aren’t needed. (Miško has more info on NodeInjectors) - ModuleInjectors have no visibility - All bindings and instances of parent injectors are now visible in child injectors. NodeInjectors do, however, have some sense of visibility. - The optional argument “forceNewInstances” of Injector.createChild has been removed Instead, create a new module with bindings of the types that require new instances and pass that to the child injector, and the child injector will create new instances instead of returning the instance of the parent injector. - Use “new ModuleInjector(modules, parent)” instead of “Injector.createChild(modules)” - The latter is still available but deprecated. - Injectors with no parent now have a dummy RootInjector instance as the parent Instead of checking “parent == null”, check for “parent == rootInjector”. - Injectors no longer have a name field - typeFactories have changed - Old type factories had the form (injector) => new Instance(injector.get(dep1), … ) - New factories have one of: - toFactory(a0, a1) => new Instance(a0, a1) - When calling Module.bind(toFactory: factory), there is an additional argument “inject” of a list of types or keys (preferred for performance) whose instances should be passed to the factory. The array “p” passed to the factory function will be instances of the types in “inject”. Example: - Old code. module.bind(Car, toFactory: (i) => new Car(i.get(Engine))); - New code. - module.bind(Car, toFactory: (engine) => new Car(engine), inject: [Engine]); There is also some syntactic sugar for this special case. - Old code. module.bind(Engine, toFactory: (i) => i.get(Engine)); - New code. module.bind(Engine, toFactory: (e) => e, inject: [Engine]); - With sugar. module.bind(Engine, toInstanceOf: Engine); - Modules have a TypeReflector instance attached - The TypeReflector is how the module will find the “toFactory” and “inject” arguments when not explicitly specified. This is either done with mirroring or code generation via transformers. Typical use will not need to worry about this at all, and the default reflector will suffice. If needed, implement TypeReflector and use new Module.withReflector(reflector). - The transformer has been updated - Running the transformer will do the necessary code generation and edits to switch the default TypeReflector from mirroring to static factories. Enable transformer to use static factories, disable to use mirrors. More docs on the transformer can be found in transformer.dart - And it’s lots faster! --- .gitignore | 2 + CHANGELOG.md | 1 - LICENSE | 2 +- README.md | 20 +- benchmark/dynamic_injector_benchmark.dart | 7 +- benchmark/injector_benchmark_common.dart | 73 ++- benchmark/instance_benchmark.dart | 17 +- benchmark/large_benchmark.dart | 70 +++ benchmark/module_benchmark.dart | 17 +- .../static_injector_baseline_benchmark.dart | 119 ++++ benchmark/static_injector_benchmark.dart | 16 +- example/pubspec.yaml | 10 + example/web/index.html | 11 + example/web/main.dart | 16 + lib/auto_injector.dart | 58 -- lib/check_bind_args.dart | 65 ++ lib/di.dart | 16 +- lib/dynamic_injector.dart | 96 +-- lib/generator.dart | 158 +++-- lib/key.dart | 21 +- lib/module_transformer.dart | 35 ++ lib/src/base_injector.dart | 257 -------- lib/src/error_helper.dart | 31 - lib/src/errors.dart | 63 +- lib/src/injector.dart | 228 +++++-- lib/src/injector_delagate.dart | 63 -- lib/src/module.dart | 236 +++---- lib/src/provider.dart | 51 -- lib/src/reflector.dart | 34 ++ lib/src/reflector_dynamic.dart | 235 +++++++ lib/src/reflector_null.dart | 22 + lib/src/reflector_static.dart | 38 ++ lib/static_injector.dart | 64 -- lib/transformer.dart | 32 +- lib/transformer/injector_generator.dart | 113 ++-- lib/transformer/options.dart | 21 +- lib/transformer/refactor.dart | 136 ----- pubspec.yaml | 18 +- run-benchmarks.sh | 8 +- run-tests.sh | 59 +- scripts/check_bind_args_script.dart | 19 + scripts/class_gen.dart | 45 ++ scripts/reflector_dynamic_script.dart | 15 + test/auto_injector_test.dart | 10 - test/generator_test.dart | 49 -- test/main.dart | 575 +++++++----------- test/main_same_name.dart | 6 + ...erator_spec.dart => transformer_test.dart} | 284 +++++---- test_tf_gen.sh | 2 +- 49 files changed, 1833 insertions(+), 1711 deletions(-) create mode 100644 benchmark/large_benchmark.dart create mode 100644 benchmark/static_injector_baseline_benchmark.dart create mode 100644 example/pubspec.yaml create mode 100644 example/web/index.html create mode 100644 example/web/main.dart delete mode 100644 lib/auto_injector.dart create mode 100644 lib/check_bind_args.dart create mode 100644 lib/module_transformer.dart delete mode 100644 lib/src/base_injector.dart delete mode 100644 lib/src/error_helper.dart delete mode 100644 lib/src/injector_delagate.dart delete mode 100644 lib/src/provider.dart create mode 100644 lib/src/reflector.dart create mode 100644 lib/src/reflector_dynamic.dart create mode 100644 lib/src/reflector_null.dart create mode 100644 lib/src/reflector_static.dart delete mode 100644 lib/static_injector.dart delete mode 100644 lib/transformer/refactor.dart create mode 100644 scripts/check_bind_args_script.dart create mode 100644 scripts/class_gen.dart create mode 100644 scripts/reflector_dynamic_script.dart delete mode 100644 test/auto_injector_test.dart delete mode 100644 test/generator_test.dart create mode 100644 test/main_same_name.dart rename test/{injector_generator_spec.dart => transformer_test.dart} (75%) diff --git a/.gitignore b/.gitignore index f98e4bd..4850d16 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ test/main.dart.js.deps test/main.dart.js.map test/main.dart.precompiled.js test/transformer/build +benchmark/generated_files/ +examples/build out build benchmark/generated_files/* diff --git a/CHANGELOG.md b/CHANGELOG.md index ab040b9..bd4592a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,6 @@ Added missing library declaration to injector. - **injector:** optimized module to injector instantiation ([62f22f15](https://github.com/angular/di.dart/commit/62f22f1566642cecc1b9f980475c94a7a88e9362)) - # 1.0.0 Starting with this release DI is following [semver](http://semver.org). diff --git a/LICENSE b/LICENSE index e950988..47c5c22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2013 Google, Inc. +Copyright (c) 2014 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2609ba0..05e899e 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,29 @@ Add dependency to your pubspec.yaml. dependencies: - di: ">=0.0.39 <0.1.0" + di: ">=2.0.0 <3.0.0" Then, run `pub install`. Import di. import 'package:di/di.dart'; - import 'package:di/auto_injector.dart'; ## Example ```dart import 'package:di/di.dart'; -import 'package:di/auto_injector.dart'; abstract class Engine { go(); } +class Fuel {} + class V8Engine implements Engine { + Fuel fuel; + V8Engine(this.fuel); + go() { print('Vroom...'); } @@ -64,14 +67,17 @@ class ElectricCar { } void main() { - var injector = defaultInjector(modules: [new Module() + var injector = new ModuleInjector(modules: [new Module() ..bind(GenericCar) ..bind(ElectricCar) - ..bind(Engine, toFactory: (i) => new V8Engine()) + ..bind(Engine, toFactory: (fuel) => new V8Engine(fuel), inject: [Fuel]) ..bind(Engine, toImplementation: ElectricEngine, withAnnotation: Electric) ]); - injector.get(GenericCar).drive(); - injector.get(ElectricCar).drive(); + injector.get(GenericCar).drive(); // Vroom... + injector.get(ElectricCar).drive(); // Hum... } ``` +## Contributing + +Refer to the guidelines for [contributing to AngularDart](http://goo.gl/nrXVgm). diff --git a/benchmark/dynamic_injector_benchmark.dart b/benchmark/dynamic_injector_benchmark.dart index a5fa472..22e81b9 100644 --- a/benchmark/dynamic_injector_benchmark.dart +++ b/benchmark/dynamic_injector_benchmark.dart @@ -1,10 +1,9 @@ import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:di/dynamic_injector.dart'; +import 'package:di/src/reflector_dynamic.dart'; import 'injector_benchmark_common.dart'; - main() { new InjectorBenchmark('DynamicInjectorBenchmark', - (m) => new DynamicInjector(modules: m)).report(); -} \ No newline at end of file + new DynamicTypeFactories()).report(); +} diff --git a/benchmark/injector_benchmark_common.dart b/benchmark/injector_benchmark_common.dart index ce1cbd9..882ac10 100644 --- a/benchmark/injector_benchmark_common.dart +++ b/benchmark/injector_benchmark_common.dart @@ -6,32 +6,43 @@ import 'package:di/di.dart'; int count = 0; class InjectorBenchmark extends BenchmarkBase { - var injectorFactory; var module; + var typeReflector; + Key KEY_A; + Key KEY_B; + Key KEY_C; + Key KEY_D; + Key KEY_E; - InjectorBenchmark(name, this.injectorFactory) : super(name); + InjectorBenchmark(name, this.typeReflector) : super(name); void run() { - Injector injector = injectorFactory([module]); - injector.get(A); - injector.get(B); + Injector injector = new ModuleInjector([module]); + injector.getByKey(KEY_A); + injector.getByKey(KEY_B); - var childInjector = injector.createChild([module]); - childInjector.get(A); - childInjector.get(B); + var childInjector = new ModuleInjector([module], injector); + childInjector.getByKey(KEY_A); + childInjector.getByKey(KEY_B); } setup() { - module = new Module() - ..type(A) - ..type(B) - ..type(C) - ..type(C, withAnnotation: AnnOne, implementedBy: COne ) - ..type(D) - ..type(E) - ..type(E, withAnnotation: AnnTwo, implementedBy: ETwo ) - ..type(F) - ..type(G); + module = new Module.withReflector(typeReflector) + ..bind(A) + ..bind(B) + ..bind(C) + ..bind(C, withAnnotation: AnnOne, toImplementation: COne ) + ..bind(D) + ..bind(E) + ..bind(E, withAnnotation: AnnTwo, toImplementation: ETwo ) + ..bind(F) + ..bind(G); + + KEY_A = new Key(A); + KEY_B = new Key(B); + KEY_C = new Key(C); + KEY_D = new Key(D); + KEY_E = new Key(E); } teardown() { @@ -96,7 +107,31 @@ class F { } class G { - G(@AnnTwo() E) { + G(@AnnTwo() E e) { count++; } } + +var typeFactories = { + A: (a1, a2) => new A(a1, a2), + B: (a1, a2) => new B(a1, a2), + C: () => new C(), + D: () => new D(), + E: () => new E(), + COne: () => new COne(), + ETwo: () => new ETwo(), + F: (a1, a2) => new F(a1, a2), + G: (a1) => new G(a1), +}; + +var paramKeys = { + A: [new Key(B), new Key(C)], + B: [new Key(D), new Key(E)], + C: const [], + D: const [], + E: const [], + COne: const [], + ETwo: const [], + F: [new Key(C, AnnOne), new Key(D)], + G: [new Key(G, AnnTwo)], +}; diff --git a/benchmark/instance_benchmark.dart b/benchmark/instance_benchmark.dart index 291048e..56f9cbf 100644 --- a/benchmark/instance_benchmark.dart +++ b/benchmark/instance_benchmark.dart @@ -1,13 +1,14 @@ -import 'package:di/static_injector.dart'; +import 'package:di/di.dart'; +import 'package:di/src/reflector_static.dart'; import 'injector_benchmark_common.dart'; // tests the speed of cached getInstanceByKey requests class InstanceBenchmark extends InjectorBenchmark{ - InstanceBenchmark(name, injectorFactory) : super(name, injectorFactory); + InstanceBenchmark(name, typeReflector) : super(name, typeReflector); void run(){ - Injector injector = injectorFactory([module]); + Injector injector = new ModuleInjector([module]); for (var i = 0; i < 30; i++) { injector.get(A); } @@ -15,15 +16,7 @@ class InstanceBenchmark extends InjectorBenchmark{ } main() { - var typeFactories = { - A: (f) => new A(f(B), f(C)), - B: (f) => new B(f(D), f(E)), - C: (f) => new C(), - D: (f) => new D(), - E: (f) => new E(), - }; - new InstanceBenchmark('InstanceBenchmark', - (m) => new StaticInjector(modules: m, typeFactories: typeFactories) + new GeneratedTypeFactories(typeFactories, paramKeys) ).report(); } diff --git a/benchmark/large_benchmark.dart b/benchmark/large_benchmark.dart new file mode 100644 index 0000000..612c9a5 --- /dev/null +++ b/benchmark/large_benchmark.dart @@ -0,0 +1,70 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:di/di.dart'; +import 'package:di/src/reflector_static.dart'; +import 'generated_files/factories.dart'; + +import 'dart:math'; + +class LargeBenchmark extends BenchmarkBase { + var injectorFactory; + Injector rootInjector; + Injector leafInjector; + var leafKey; + var rng = new Random(); + var numInjectors = 1; + var allLeaves = []; + + LargeBenchmark(name, this.injectorFactory) : super("Large" + name); + + setup() { + var rootModule = new Module() + ..bindByKey(key999) + ..bindByKey(key998) + ..bindByKey(key997) + ..bindByKey(key996) + ..bindByKey(key995); + rootInjector = injectorFactory([rootModule]); + + createChildren (injector, depth, width) { + if (depth <= 0){ + allLeaves.add(injector); + return; + } + for (var i=0; i new ModuleInjector(m)); + + run() { + leafInjector.getByKey(key999); + } +} + +class GetFromLeaf extends LargeBenchmark { + GetFromLeaf() : super('FromLeaf', (m) => new ModuleInjector(m)); + + run() { + leafInjector.getByKey(leafKey); + } +} + +main() { + Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys); + new GetFromRoot().report(); + new GetFromLeaf().report(); +} diff --git a/benchmark/module_benchmark.dart b/benchmark/module_benchmark.dart index cb18a5a..aeb34a4 100644 --- a/benchmark/module_benchmark.dart +++ b/benchmark/module_benchmark.dart @@ -3,6 +3,11 @@ import 'package:di/di.dart'; import 'injector_benchmark_common.dart'; +/** + * tests the speed of looking up typeFactories and binding them to + * a module. Mirroring time is not counted because DynamicTypeFactories + * caches the results. + */ class ModuleBenchmark extends BenchmarkBase { var injectorFactory; @@ -10,14 +15,14 @@ class ModuleBenchmark extends BenchmarkBase { void run() { var m = new Module() - ..type(A) - ..type(B) - ..type(C) - ..type(D) - ..type(E); + ..bind(A) + ..bind(B) + ..bind(C) + ..bind(D) + ..bind(E); } } main() { new ModuleBenchmark().report(); -} \ No newline at end of file +} diff --git a/benchmark/static_injector_baseline_benchmark.dart b/benchmark/static_injector_baseline_benchmark.dart new file mode 100644 index 0000000..b7b7f5c --- /dev/null +++ b/benchmark/static_injector_baseline_benchmark.dart @@ -0,0 +1,119 @@ +import 'package:di/di.dart'; +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:di/src/reflector_static.dart'; + +import 'injector_benchmark_common.dart'; +import 'static_injector_benchmark.dart'; + +import 'dart:profiler'; + +/** + * This benchmark creates the same objects as the StaticInjectorBenchmark + * without using DI, to serve as a baseline for comparison. + */ +class CreateObjectsOnly extends BenchmarkBase { + CreateObjectsOnly(name) : super(name); + + void run() { + var b1 = new B(new D(), new E()); + var c1 = new C(); + var d1 = new D(); + var e1 = new E(); + + var a = new A(b1, c1); + var b = new B(d1, e1); + + var c = new A(b1, c1); + var d = new B(d1, e1); + } + + void teardown() { + print(count); + } +} + +class CreateSingleInjector extends InjectorBenchmark { + + CreateSingleInjector(name, injectorFactory) : super(name, injectorFactory); + + void run() { + Injector injector = new ModuleInjector([module]); + + var b1 = new B(new D(), new E()); + var c1 = new C(); + var d1 = new D(); + var e1 = new E(); + + var a = new A(b1, c1); + var b = new B(d1, e1); + + var c = new A(b1, c1); + var d = new B(d1, e1); + } +} + +class CreateInjectorAndChild extends InjectorBenchmark { + + CreateInjectorAndChild(name, injectorFactory) : super(name, injectorFactory); + + void run() { + Injector injector = new ModuleInjector([module]); + var childInjector = injector.createChild([module]); + + var b1 = new B(new D(), new E()); + var c1 = new C(); + var d1 = new D(); + var e1 = new E(); + + var a = new A(b1, c1); + var b = new B(d1, e1); + + var c = new A(b1, c1); + var d = new B(d1, e1); + } +} + +class InjectByKey extends InjectorBenchmark { + + InjectByKey(name, injectorFactory) + : super(name, injectorFactory); + + void run() { + var injector = new ModuleInjector([module]); + var childInjector = new ModuleInjector([module], injector); + + injector.getByKey(KEY_A); + injector.getByKey(KEY_B); + + childInjector.getByKey(KEY_A); + childInjector.getByKey(KEY_B); + } +} + +main() { + var oldTypeFactories = { + A: (f) => new A(f(B), f(C)), + B: (f) => new B(f(D), f(E)), + C: (f) => new C(), + D: (f) => new D(), + E: (f) => new E(), + }; + + const PAD_LENGTH = 35; + GeneratedTypeFactories generatedTypeFactories = + new GeneratedTypeFactories(typeFactories, paramKeys); + + new CreateObjectsOnly("Create objects manually without DI".padRight(PAD_LENGTH)).report(); + new CreateSingleInjector('.. and create an injector'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); + new CreateInjectorAndChild('.. and a child injector'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); + new InjectorBenchmark('DI using ModuleInjector'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); + new InjectByKey('.. and precompute keys'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); +} diff --git a/benchmark/static_injector_benchmark.dart b/benchmark/static_injector_benchmark.dart index b9d4efb..d4f1bcd 100644 --- a/benchmark/static_injector_benchmark.dart +++ b/benchmark/static_injector_benchmark.dart @@ -1,17 +1,9 @@ -import 'package:di/static_injector.dart'; - +import 'package:di/di.dart'; +import 'package:di/src/reflector_static.dart'; import 'injector_benchmark_common.dart'; main() { - var typeFactories = { - A: (f) => new A(f(B), f(C)), - B: (f) => new B(f(D), f(E)), - C: (f) => new C(), - D: (f) => new D(), - E: (f) => new E(), - }; - new InjectorBenchmark('StaticInjectorBenchmark', - (m) => new StaticInjector(modules: m, typeFactories: typeFactories) + new GeneratedTypeFactories(typeFactories, paramKeys) ).report(); -} \ No newline at end of file +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..6e89349 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,10 @@ +name: di_example +version: 0.0.1 +dependencies: + browser: any + web_components: any + di: + path: ../ + +transformers: +- di diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 0000000..fada8c8 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,11 @@ + + + + Example + + + + +Check console output for build success. + + diff --git a/example/web/main.dart b/example/web/main.dart new file mode 100644 index 0000000..d5e0160 --- /dev/null +++ b/example/web/main.dart @@ -0,0 +1,16 @@ +import 'package:di/di.dart'; +import 'package:di/annotations.dart'; +import 'dart:html'; + +@Injectable() +class Application { + run() { + print('Success'); + } +} + +main() { + Module module = new Module(); + module.bind(Application); + new ModuleInjector([module]).get(Application).run(); +} diff --git a/lib/auto_injector.dart b/lib/auto_injector.dart deleted file mode 100644 index 622ed25..0000000 --- a/lib/auto_injector.dart +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Library for using a pub transformer to automatically switch between - * dynamic and static injection. - * - * ## Step 1: Hook up the build step - * Edit ```pubspec.yaml``` to add the di transformer to the list of - * transformers. - * - * name: transformer_demo - * version: 0.0.1 - * dependencies: - * di: any - * inject: any - * transformers: - * - di: - * dart_entry: web/main.dart - * injectable_annotations: transformer_demo.Injectable - * - * ## Step 2: Annotate your types - * - * class Engine { - * @inject - * Engine(); - * } - * - * or - * - * @Injectable // custom annotation specified in pubspec.yaml - * class Car {} - * - * - * ## Step 3: Use the auto injector - * Modify your entry script to use [defaultInjector] as the injector. - * - * This must be done from the file registered as the dart_entry in pubspec.yaml - * as this is the only file which will be modified to include the generated - * injector. - * - * import 'package:di/auto_injector' as auto; - * main() { - * var injector = auto.defaultInjector(modules: ...); - * } - } - */ -library di.auto_injector; - -import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; - -@MirrorsUsed(override: '*') -import 'dart:mirrors' show MirrorsUsed; - -Injector defaultInjector({List modules, String name, - bool allowImplicitInjection: false}) => - new DynamicInjector( - modules: modules, - name: name, - allowImplicitInjection: allowImplicitInjection); diff --git a/lib/check_bind_args.dart b/lib/check_bind_args.dart new file mode 100644 index 0000000..48e6c55 --- /dev/null +++ b/lib/check_bind_args.dart @@ -0,0 +1,65 @@ +library di.check_bind_args; + +import "src/module.dart"; +export "src/module.dart" show DEFAULT_VALUE, IDENTITY, isSet, isNotSet; + +checkBindArgs(dynamic toValue, Function toFactory, + Type toImplementation, List inject) { + int count = 0; + bool argCountMatch = true; + if (isSet(toValue)) count++; + if (isSet(toFactory)) { + count++; + var len = inject.length; + switch (len) { + case 0: argCountMatch = toFactory is _0; break; + case 1: argCountMatch = toFactory is _1; break; + case 2: argCountMatch = toFactory is _2; break; + case 3: argCountMatch = toFactory is _3; break; + case 4: argCountMatch = toFactory is _4; break; + case 5: argCountMatch = toFactory is _5; break; + case 6: argCountMatch = toFactory is _6; break; + case 7: argCountMatch = toFactory is _7; break; + case 8: argCountMatch = toFactory is _8; break; + case 9: argCountMatch = toFactory is _9; break; + case 10: argCountMatch = toFactory is _10; break; + case 11: argCountMatch = toFactory is _11; break; + case 12: argCountMatch = toFactory is _12; break; + case 13: argCountMatch = toFactory is _13; break; + case 14: argCountMatch = toFactory is _14; break; + case 15: argCountMatch = toFactory is _15; break; + } + if (!argCountMatch) throw "toFactory's argument count does not match amount provided by inject"; + } + + if (toImplementation != null) count++; + if (count > 1) { + throw 'Only one of following parameters can be specified: ' + 'toValue, toFactory, toImplementation'; + } + + if (inject.isNotEmpty && isNotSet(toFactory)) { + throw "Received inject list but toFactory is not set."; + } + + return true; +} + +typedef _0(); +typedef _1(a1); +typedef _2(a1, a2); +typedef _3(a1, a2, a3); +typedef _4(a1, a2, a3, a4); +typedef _5(a1, a2, a3, a4, a5); +typedef _6(a1, a2, a3, a4, a5, a6); +typedef _7(a1, a2, a3, a4, a5, a6, a7); +typedef _8(a1, a2, a3, a4, a5, a6, a7, a8); +typedef _9(a1, a2, a3, a4, a5, a6, a7, a8, a9); +typedef _10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +typedef _11(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); +typedef _12(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); +typedef _13(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); +typedef _14(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); +typedef _15(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + +// Generation script in scripts/check_bind_args_script.dart diff --git a/lib/di.dart b/lib/di.dart index 5579d73..e0022b9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,12 +1,8 @@ library di; -import 'src/provider.dart'; -import 'key.dart'; - -export 'key.dart' show Key; - -import 'src/injector.dart'; -export 'src/injector.dart'; -part 'src/module.dart'; -part 'src/errors.dart'; - +export 'key.dart' show Key, key; +export 'src/injector.dart' show Injector, ModuleInjector; +export 'src/module.dart' show Module, Binding, DEFAULT_VALUE; +export 'src/reflector.dart' show TypeReflector; +export 'src/errors.dart' hide BaseError, PRIMITIVE_TYPES; +export 'annotations.dart'; diff --git a/lib/dynamic_injector.dart b/lib/dynamic_injector.dart index 3349ea2..ebaf9e3 100644 --- a/lib/dynamic_injector.dart +++ b/lib/dynamic_injector.dart @@ -1,99 +1,13 @@ library di.dynamic_injector; import 'di.dart'; -import 'src/mirrors.dart'; -import 'src/base_injector.dart'; -import 'src/error_helper.dart'; -import 'src/provider.dart'; - export 'di.dart'; /** - * Dynamic implementation of [Injector] that uses mirrors. + * A backwards-compatible shim to avoid breaking DI 1 with DI 2.0.0 + * TODO: Remove after all apps have been upgraded. */ -class DynamicInjector extends BaseInjector { - - DynamicInjector({List modules, String name, - bool allowImplicitInjection: false}) - : super(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection); - - DynamicInjector._fromParent(List modules, Injector parent, {name}) - : super.fromParent(modules, parent, name: name); - - newFromParent(List modules, String name) => - new DynamicInjector._fromParent(modules, this, name: name); - - Object newInstanceOf(Type type, ObjectFactory objFactory, Injector requestor, - ResolutionContext resolving) { - var classMirror = reflectType(type); - if (classMirror is TypedefMirror) { - throw new NoProviderError(error(resolving, 'No implementation provided ' - 'for ${getSymbolName(classMirror.qualifiedName)} typedef!')); - } - - MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; - - if (ctor == null) { - throw new NoProviderError('Unable to find default constructor for $type. ' - 'Make sure class has a default constructor.' + (1.0 is int ? - 'Make sure you have correctly configured @MirrorsUsed.' : '')); - } - - resolveArgument(int pos) { - ParameterMirror p = ctor.parameters[pos]; - if (p.type.qualifiedName == #dynamic) { - var name = MirrorSystem.getName(p.simpleName); - throw new NoProviderError( - error(resolving, "The '$name' parameter must be typed")); - } - if (p.type is TypedefMirror) { - throw new NoProviderError( - error(resolving, - 'Cannot create new instance of a typedef ${p.type}')); - } - if (p.metadata.isNotEmpty) { - assert(p.metadata.length == 1); - var type = p.metadata.first.type.reflectedType; - return objFactory.getInstanceByKey( - new Key((p.type as ClassMirror).reflectedType, type), - requestor, resolving); - } else { - return objFactory.getInstanceByKey( - new Key((p.type as ClassMirror).reflectedType), - requestor, resolving); - } - } - - var args = new List.generate(ctor.parameters.length, resolveArgument, - growable: false); - return classMirror.newInstance(ctor.constructorName, args).reflectee; - } - - /** - * Invoke given function and inject all its arguments. - * - * Returns whatever the function returns. - */ - dynamic invoke(Function fn) { - ClosureMirror cm = reflect(fn); - MethodMirror mm = cm.function; - int position = 0; - List args = mm.parameters.map((ParameterMirror parameter) { - try { - if (parameter.metadata.isNotEmpty) { - var annotation = parameter.metadata[0].type.reflectedType; - return get((parameter.type as ClassMirror).reflectedType, annotation); - } else { - return get((parameter.type as ClassMirror).reflectedType); - } - } on NoProviderError catch (e) { - throw new NoProviderError(e.message); - } finally { - position++; - } - }).toList(); - - return cm.apply(args).reflectee; - } +@Deprecated("3.0") +class DynamicInjector extends ModuleInjector { + DynamicInjector({modules}) : super(modules); } diff --git a/lib/generator.dart b/lib/generator.dart index 7ebb1a9..6214057 100644 --- a/lib/generator.dart +++ b/lib/generator.dart @@ -1,3 +1,13 @@ +/** + * Generates Factory & paramKeys maps into a file by crawling source files and + * finding all constructors that are annotated for injection. Does the same thing as + * transformer.dart for when transformers are not available, except without modifications + * to the main function and DI. As such, the user needs to import the generated file, and run + * `Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys)` + * imported, before any modules are initialized. Import 'di_static.dart' instead of 'di.dart' + * to avoid mirrors, and import 'reflector_static.dart' for GeneratedTypeFactories. + * See transformer.dart for more info. + */ library di.generator; import 'package:analyzer/src/generated/java_io.dart'; @@ -48,10 +58,12 @@ main(List args) { Map generateCode(String entryPoint, List classAnnotations, String pathToSdk, List packageRoots, String outputFilename) { + var c = new SourceCrawler(pathToSdk, packageRoots); List imports = []; Map> typeFactoryTypes = >{}; Map typeToImport = new Map(); + c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source) { new CompilationUnitVisitor(c.context, source, classAnnotations, imports, typeToImport, typeFactoryTypes, outputFilename).visit(compilationUnit, source); @@ -61,8 +73,12 @@ Map generateCode(String entryPoint, List classAnnotations Map printLibraryCode(Map typeToImport, List imports, Map> typeFactoryTypes) { + Map factories = {}; + Map keys = {}; + Map paramLists = {}; Map result = {}; + typeFactoryTypes.forEach((Chunk chunk, List classes) { List requiredImports = []; String resolveClassIdentifier(InterfaceType type) { @@ -76,64 +92,115 @@ Map printLibraryCode(Map typeToImport, String prefix = _calculateImportPrefix(import, imports); return '$prefix.${type.name}'; } + factories[chunk] = new StringBuffer(); - classes.forEach((ClassElement clazz) { - StringBuffer factory = new StringBuffer(); - bool skip = false; - factory.write('${resolveClassIdentifier(clazz.type)}: (f) => '); - factory.write('new ${resolveClassIdentifier(clazz.type)}('); - ConstructorElement constr = - clazz.constructors.firstWhere((c) => c.name.isEmpty, - orElse: () { - throw 'Unable to find default constructor for ' - '$clazz in ${clazz.source}'; - }); - factory.write(constr.parameters.map((param) { + keys[chunk] = new StringBuffer(); + paramLists[chunk] = new StringBuffer(); + + process_classes(classes, keys[chunk], factories[chunk], paramLists[chunk], + resolveClassIdentifier); + + StringBuffer code = new StringBuffer(); + String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; + code.write('library di.generated.type_factories$libSuffix;\n'); + requiredImports.forEach((import) { + String prefix = _calculateImportPrefix(import, imports); + code.write ('import "$import" as $prefix;\n'); + }); + code..write('import "package:di/key.dart" show Key;\n') + ..write(keys[chunk]) + ..write('Map typeFactories = {\n${factories[chunk]}};\n') + ..write('Map> parameterKeys = {\n${paramLists[chunk]}};\n') + ..write('main() {}\n'); + result[chunk] = code.toString(); + }); + + return result; +} + +typedef String IdentifierResolver(InterfaceType type); +/** + * Takes classes and writes to StringBuffers the corresponding keys, factories, + * and paramLists needed for static injection. + * + * resolveClassIdentifier is a function passed in to be called to resolve imports + */ +void process_classes(Iterable classes, StringBuffer keys, + StringBuffer factories, StringBuffer paramLists, + IdentifierResolver resolveClassIdentifier) { + + Map toBeAdded = new Map(); + Set addedKeys = new Set(); + classes.forEach((ClassElement clazz) { + StringBuffer factory = new StringBuffer(); + StringBuffer paramList = new StringBuffer(); + List factoryKeys = new List(); + bool skip = false; + if (addedKeys.add(getUniqueName(clazz.type))) { + toBeAdded[getUniqueName(clazz.type)] = + 'final Key _KEY_${getUniqueName(clazz.type)} = new Key(${resolveClassIdentifier(clazz.type)});\n'; + } + factoryKeys.add(getUniqueName(clazz.type)); + + ConstructorElement constr = + clazz.constructors.firstWhere((c) => c.name.isEmpty, + orElse: () { + throw 'Unable to find default constructor for ' + '$clazz in ${clazz.source}'; + }); + var args = new List.generate(constr.parameters.length, (i) => 'a$i').join(', '); + factory.write('${resolveClassIdentifier(clazz.type)}: ($args) => ' + 'new ${resolveClassIdentifier(clazz.type)}($args),\n'); + + paramList.write('${resolveClassIdentifier(clazz.type)}: '); + if (constr.parameters.isEmpty){ + paramList.write('const ['); + } else { + paramList.write('['); + paramList.write(constr.parameters.map((param) { if (param.type.element is! ClassElement) { throw 'Unable to resolve type for constructor parameter ' - '"${param.name}" for type "$clazz" in ${clazz.source}'; + '"${param.name}" for type "$clazz" in ${clazz.source}'; } if (_isParameterized(param)) { print('WARNING: parameterized types are not supported: ' - '$param in $clazz in ${clazz.source}. Skipping!'); + '$param in $clazz in ${clazz.source}. Skipping!'); skip = true; } var annotations = []; if (param.metadata.isNotEmpty) { annotations = param.metadata.map( - (item) => resolveClassIdentifier(item.element.returnType)); + (item) => item.element.returnType.name); } - StringBuffer output = - new StringBuffer('f(${resolveClassIdentifier(param.type)}'); - if (annotations.isNotEmpty) { - output.write(', ${annotations.first}'); + String key_name = annotations.isNotEmpty ? + '${getUniqueName(param.type)}_${annotations.first}' : getUniqueName(param.type); + String output = '_KEY_${key_name}'; + if (addedKeys.add(key_name)){ + var annotationParam = ""; + if (param.metadata.isNotEmpty) { + var p = resolveClassIdentifier(param.metadata.first.element.returnType); + annotationParam = ", $p"; + } + toBeAdded['$key_name'] ='final Key _KEY_${key_name} = ' + 'new Key(${resolveClassIdentifier(param.type)}$annotationParam);\n'; } - output.write(')'); return output; }).join(', ')); - factory.write('),\n'); - if (!skip) { - factories[chunk].write(factory); - } - }); - StringBuffer code = new StringBuffer(); - String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; - code.write('library di.generated.type_factories$libSuffix;\n'); - requiredImports.forEach((import) { - String prefix = _calculateImportPrefix(import, imports); - code.write ('import "$import" as $prefix;\n'); - }); - code..write('var typeFactories = {\n${factories[chunk]}\n};\n') - ..write('main() {}\n'); - result[chunk] = code.toString(); + } + paramList.write('],\n'); + if (!skip) { + factoryKeys.forEach((key) { + var keyString = toBeAdded.remove(key); + keys.write(keyString); + }); + factories.write(factory); + paramLists.write(paramList); + } }); - - return result; + keys.writeAll(toBeAdded.values); + toBeAdded.clear(); } -String _calculateImportPrefix(String import, List imports) => - 'import_${imports.indexOf(import)}'; - _isParameterized(ParameterElement param) { String typeName = param.type.toString(); @@ -145,6 +212,10 @@ _isParameterized(ParameterElement param) { return false; } + +String _calculateImportPrefix(String import, List imports) => + 'import_${imports.indexOf(import)}'; + class CompilationUnitVisitor { List imports; Map typeToImport; @@ -241,6 +312,13 @@ String getQualifiedName(InterfaceType type) { return lib == null ? name : '$lib.$name'; } +String getUniqueName(InterfaceType type) { + var lib = type.element.library.displayName; + var name = type.name; + String qualName = lib == null ? name : '$lib.$name'; + return qualName.replaceAll('.', '_'); +} + String getCanonicalName(InterfaceType type) { var source = type.element.source.toString(); var name = type.name; diff --git a/lib/key.dart b/lib/key.dart index d488212..2b61de3 100644 --- a/lib/key.dart +++ b/lib/key.dart @@ -1,10 +1,14 @@ library di.key; +import 'dart:collection'; + /** * Key to which an [Injector] binds a [Provider]. This is a pair consisting of * a [type] and an optional [annotation]. */ class Key { + // TODO: experiment with having a separate map for non-annotated types (perf) + // While Map.identity is faster here, it's not supported in dart2js (dart issue 19622 wontfix) static Map> _typeToAnnotationToKey = {}; static int _numInstances = 0; /// The number of instances of [Key] created. @@ -16,6 +20,18 @@ class Key { /// Assigned via auto-increment. final int id; + int _data; + @deprecated + int get uid => _data; + @deprecated + set uid(int d) { + if (_data == null) { + _data = d; + return; + } + throw "Key($type).uid has already been set to $_data."; + } + int get hashCode => id; /** @@ -26,7 +42,7 @@ class Key { // Don't use Map.putIfAbsent -- too slow! var annotationToKey = _typeToAnnotationToKey[type]; if (annotationToKey == null) { - _typeToAnnotationToKey[type] = annotationToKey = {}; + _typeToAnnotationToKey[type] = annotationToKey = new Map(); } Key key = annotationToKey[annotation]; if (key == null) { @@ -46,3 +62,6 @@ class Key { return asString; } } + +/// shortcut function +Key key(Type type, [Type annotation]) => new Key(type, annotation); diff --git a/lib/module_transformer.dart b/lib/module_transformer.dart new file mode 100644 index 0000000..5992a6e --- /dev/null +++ b/lib/module_transformer.dart @@ -0,0 +1,35 @@ +library di.transformer.module_transformer; + +import 'dart:async'; +import 'package:barback/barback.dart'; + +/** + * Pub transformer that changes reflector in Module to null instead of importing + * the dynamic reflector which imports mirrors. Another di transformer run by the app + * will import the static reflector. + */ +class ModuleTransformerGroup implements TransformerGroup { + final Iterable phases; + + ModuleTransformerGroup.asPlugin(BarbackSettings settings) + : phases = [[new ModuleTransformer()]]; +} + +class ModuleTransformer extends Transformer { + + ModuleTransformer(); + + Future isPrimary(AssetId id) => + new Future.value(id == new AssetId.parse("di|lib/src/module.dart")); + + Future apply(Transform transform) { + var id = transform.primaryInput.id; + return transform.primaryInput.readAsString().then((code) { + // Note: this rewrite is coupled with how module.dart is + // written. Make sure both are updated in sync. + transform.addOutput(new Asset.fromString(id, code + .replaceAll(new RegExp('import "reflector_dynamic.dart";'), + 'import "reflector_null.dart";'))); + }); + } +} diff --git a/lib/src/base_injector.dart b/lib/src/base_injector.dart deleted file mode 100644 index 4a79e02..0000000 --- a/lib/src/base_injector.dart +++ /dev/null @@ -1,257 +0,0 @@ -library di.base_injector; - -import 'provider.dart'; -import 'error_helper.dart'; - -import 'package:collection/collection.dart'; -import 'package:di/di.dart'; -import 'package:di/key.dart'; - -List _PRIMITIVE_TYPES = new UnmodifiableListView([ - new Key(num), new Key(int), new Key(double), new Key(String), - new Key(bool) -]); - -abstract class BaseInjector implements Injector, ObjectFactory { - - @deprecated - final String name; - - @override - final BaseInjector parent; - - @deprecated - Injector _root; - - List _providers; - - /** - * _instances is a List when implicit injection is not allowed - * for performance because all types would have been seen and the - * List would not need to be expanded. In dynamic injection with - * implicit injection turned on it is a Map instead. - */ - List _instancesList; - Map _instancesMap; - - @deprecated - final bool allowImplicitInjection; - - Iterable _typesCache; - - Iterable get _types { - if (_providers == null) return []; - - if (_typesCache == null) { - _typesCache = _providers - .where((p) => p != null) - .map((p) => p.type); - } - return _typesCache; - } - - BaseInjector({List modules, String name, - bool allowImplicitInjection: false}) - : this.fromParent(modules, null, - name: name, allowImplicitInjection: allowImplicitInjection); - - BaseInjector.fromParent(List modules, - BaseInjector this.parent, {this.name, this.allowImplicitInjection: false}) { - _root = parent == null ? this : parent._root; - var injectorId = new Key(Injector).id; - _providers = new List(Key.numInstances); - - if (allowImplicitInjection) { - _instancesMap = new Map(); - } else { - _instancesList = new List(Key.numInstances); - } - if (modules != null) { - modules.forEach((module) { - module.updateListWithBindings(_providers); - }); - } - _providers[injectorId] = new ValueProvider(Injector, this); - } - - @deprecated - Injector get root => _root; - - @override - Set get types { - var types = new Set(); - for (var node = this; node != null; node = node.parent) { - types.addAll(node._types); - } - return types; - } - - @override - Object getInstanceByKey(Key key, Injector requester, ResolutionContext resolving) { - assert(_checkKeyConditions(key, resolving)); - - // Do not bother checking the array until we are fairly deep. - if (resolving.depth > 30 && resolving.ancestorKeys.contains(key)) { - throw new CircularDependencyError( - error(resolving, 'Cannot resolve a circular dependency!', key)); - } - - var providerWithInjector = _getProviderWithInjectorForKey(key, resolving); - var provider = providerWithInjector.provider; - var injector = providerWithInjector.injector; - var visible = provider.visibility == null || - provider.visibility(requester, injector); - - assert(allowImplicitInjection || key.id < _instancesList.length); - if (visible){ - var instance = allowImplicitInjection ? - _instancesMap[key.id] : _instancesList[key.id]; - if (instance != null){ - return instance; - } - } - - if (injector != this || !visible) { - if (!visible) { - if (injector.parent == null) { - throw new NoProviderError( - error(resolving, 'No provider found for ${key}!', key)); - } - injector = injector.parent - ._getProviderWithInjectorForKey(key, resolving).injector; - } - return injector.getInstanceByKey(key, requester, resolving); - } - - resolving = new ResolutionContext(resolving.depth + 1, key, resolving); - var value = provider.get(this, requester, this, resolving); - - // cache the value. - if (allowImplicitInjection == true) { - providerWithInjector.injector._instancesMap[key.id] = value; - } else { - providerWithInjector.injector._instancesList[key.id] = value; - } - return value; - } - - /** - * Finds the nearest ancestor injector that binds a [Provider] to [key] and - * returns that [Provider] and its binding [Injector]. If there is no such - * [Injector], then - * - * - if [allowImplicitInjection] is true for the root injector (not this - * injector), returns a default [Provider] and the root injector. - * - if [allowImplicitInjector] is false for the root injector, throws - * [NoProviderError]. - * - * [resolving] is only used for error reporting. - */ - _ProviderWithInjector _getProviderWithInjectorForKey( - Key key, ResolutionContext resolving) { - if (key.id < _providers.length) { - var provider = _providers[key.id]; - if (provider != null) { - return new _ProviderWithInjector(provider, this); - } - } - if (parent != null) { - return parent._getProviderWithInjectorForKey(key, resolving); - } - if (allowImplicitInjection) { - return new _ProviderWithInjector(new TypeProvider(key.type), this); - } - throw new NoProviderError( - error(resolving, 'No provider found for ${key}!', key)); - } - - bool _checkKeyConditions(Key key, ResolutionContext resolving) { - if (_PRIMITIVE_TYPES.contains(key)) { - throw new NoProviderError( - error(resolving, - 'Cannot inject a primitive type of ${key.type}!', key)); - } - return true; - } - - @override - dynamic get(Type type, [Type annotation]) => - getInstanceByKey(new Key(type, annotation), this, ResolutionContext.ROOT); - - @override - dynamic getByKey(Key key) => - getInstanceByKey(key, this, ResolutionContext.ROOT); - - @override - Injector createChild(List modules, - {List forceNewInstances, String name}) => - createChildWithResolvingHistory(modules, ResolutionContext.ROOT, - forceNewInstances: forceNewInstances, - name: name); - - Injector createChildWithResolvingHistory( - List modules, - ResolutionContext resolving, - {List forceNewInstances, String name}) { - if (forceNewInstances != null) { - Module forceNew = new Module(); - forceNewInstances.forEach((key) { - if (key is Type) { - key = new Key(key); - } else if (key is! Key) { - throw 'forceNewInstances must be List'; - } - var providerWithInjector = - _getProviderWithInjectorForKey(key, resolving); - var provider = providerWithInjector.provider; - forceNew.bindByKey(key, - toFactory: (Injector inj) => - provider.get(this, inj, inj as ObjectFactory, resolving), - visibility: provider.visibility); - }); - - modules = modules.toList(); // clone - modules.add(forceNew); - } - - return newFromParent(modules, name); - } - - newFromParent(List modules, String name); - - Object newInstanceOf(Type type, ObjectFactory factory, Injector requestor, - ResolutionContext resolving); -} - -class _ProviderWithInjector { - final Provider provider; - final BaseInjector injector; - _ProviderWithInjector(this.provider, this.injector); -} - -/** - * A node in a depth-first search tree of the dependency DAG. - */ -class ResolutionContext { - static final ResolutionContext ROOT = new ResolutionContext(0, null, null); - - /// Distance from the [ROOT]. - final int depth; - /// Key at this node or null if this is the [ROOT]. - final Key key; - /// Parent node or null if this is the [ROOT]. This node is a dependency of - /// the parent. - final ResolutionContext parent; - - ResolutionContext(this.depth, this.key, this.parent); - - /// Returns the [key]s of the ancestors of this node (including this node) in - /// the order that ascends the tree. Note that [ROOT] has no [key]. - List get ancestorKeys { - var keys = []; - for (var node = this; node.parent != null; node = node.parent) { - keys.add(node.key); - } - return keys; - } -} diff --git a/lib/src/error_helper.dart b/lib/src/error_helper.dart deleted file mode 100644 index bacde95..0000000 --- a/lib/src/error_helper.dart +++ /dev/null @@ -1,31 +0,0 @@ -library di.error_helper; - -import 'package:di/di.dart'; -import 'package:di/src/base_injector.dart'; - -/** - * Returns an error message for the given dependency chain. - * If [addDependency] is given, its [String] representation gets appended - * to the error message. - * - * Example: - * If [resolving]'s ancestors have keys k1, k2, k3 and [message] is 'foo', - * then this looks like 'foo (resolving k3 -> k2 -> k1)'. - */ -String error(ResolutionContext resolving, String message, [Key appendDependency]) { - if (appendDependency != null) { - resolving = new ResolutionContext(resolving.depth + 1, appendDependency, resolving); - } - List resolvingKeys = resolving.ancestorKeys.reversed.toList(growable: false); - // De-duplicate keys when there is a circular dependency - // This is required because we don't check for circular dependencies before the - // a depth threshold which would lead to msg like "A -> B -> A -> B -> ... -> A" - for (var i = 1; i < resolvingKeys.length - 1; i++) { - if (resolvingKeys[i] == resolvingKeys.first) { - resolvingKeys = resolvingKeys.sublist(0, i + 1); - break; - } - } - - return '$message (resolving ${resolvingKeys.join(" -> ")})'; -} diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 4b78a83..523d748 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -1,13 +1,62 @@ -part of di; +library di.errors; -class InvalidBindingError extends ArgumentError { - InvalidBindingError(message) : super(message); +import '../key.dart'; + +abstract class BaseError extends Error { + final String message; + BaseError(this.message); + String toString() => message; +} + +final List PRIMITIVE_TYPES = [ + new Key(num), new Key(int), new Key(double), new Key(String), + new Key(bool), new Key(dynamic) +]; + +class DynamicReflectorError extends BaseError { + DynamicReflectorError(message) : super(message); +} + +abstract class ResolvingError extends Error { + + List keys; + ResolvingError(key): keys = [key]; + + String get resolveChain { + StringBuffer buffer = new StringBuffer() + ..write("(resolving ") + ..write(keys.reversed.join(" -> ")) + ..write(")"); + return buffer.toString(); + } + + void appendKey(Key key) { + keys.add(key); + } + + String toString(); +} + +class NoProviderError extends ResolvingError { + NoProviderError(key): super(key); + + String toString(){ + var root = keys.first; + if (PRIMITIVE_TYPES.contains(root)) { + return "Cannot inject a primitive type of $root! $resolveChain"; + } + return "No provider found for $root! $resolveChain"; + } } -class NoProviderError extends ArgumentError { - NoProviderError(message) : super(message); +class CircularDependencyError extends ResolvingError { + CircularDependencyError(key) : super(key); + String toString() => "Cannot resolve a circular dependency! $resolveChain"; } -class CircularDependencyError extends ArgumentError { - CircularDependencyError(message) : super(message); +class NoGeneratedTypeFactoryError extends BaseError { + NoGeneratedTypeFactoryError(Type type): super(type.toString()); + String toString() => + "Type '$message' not found in generated typeFactory maps. Is the type's " + "constructor injectable and annotated for injection?"; } diff --git a/lib/src/injector.dart b/lib/src/injector.dart index 8cf6cec..fc4bdca 100644 --- a/lib/src/injector.dart +++ b/lib/src/injector.dart @@ -1,63 +1,44 @@ library di.injector; -import '../di.dart'; +import '../key.dart'; +import 'module.dart'; +import 'errors.dart'; -abstract class Injector { - /** - * Name of the injector or null if none was given. - */ - @deprecated - String get name; +final Key _INJECTOR_KEY = new Key(Injector); +class _Instance { + final String name; - /** - * The parent injector or null if root. - */ - Injector get parent; + static const _Instance EMPTY = const _Instance("EMPTY"); + static const _Instance CREATING = const _Instance("CREATING"); - /** - * The root injector. - */ - @deprecated - Injector get root; + const _Instance(this.name); + String toString() => name; +} - /** - * [Type]s of the [Provider]s explicitly bound to the injector or an ancestor. - * If the root injector sets [allowImplicitInjection] to false, then this - * is simply all the types that the injector can return. - */ - Set get types; +abstract class Injector { - /** - * Whether the injector allows injecting a type to which no [Provider] is - * bound. Note that this setting only matters for the root injector. - */ - @deprecated - bool get allowImplicitInjection; + final Injector parent = null; /** * Returns the instance associated with the given key (i.e. [type] and * [annotation]) according to the following rules. * - * Let I be the nearest ancestor injector (possibly this one) that both - * - * - binds some [Provider] P to [key] and - * - P's visibility declares that I is visible to this injector. + * Let I be the nearest ancestor injector (possibly this one) + * that has either a cached instance or a binding for [key] * - * If there is no such I, then - * - * - if [allowImplicitInjection] is true for the root injector, let I be the - * root injector and P be a default [Provider] for [type]. - * - if [allowImplicitInjection] is false for the root injector, throw + * If there is no such I, then throw * [NoProviderError]. * - * Once I and P are found, if I already created an instance for the key, - * it is returned. Otherwise, P is used to create an instance, using I - * as an [ObjectFactory] to resolve the necessary dependencies. + * Once I is found, if I already created an instance for the key, + * it is returned. Otherwise, the typeFactory of the binding is + * used to create an instance, using I as to resolve the dependencies. */ - dynamic get(Type type, [Type annotation]); + dynamic get(Type type, [Type annotation]) => + getByKey(new Key(type, annotation)); /** - * See [get]. + * Faster version of [get] by saving key creation time. Should be used instead if + * a key instance is already available. */ dynamic getByKey(Key key); @@ -65,25 +46,148 @@ abstract class Injector { * Creates a child injector. * * [modules] overrides bindings of the parent. - * - * [forceNewInstances] is a list, each element of which is a [Key] or a - * [Type] (for convenience when no annotation is needed). For each element K, - * the child injector will have a new binding to the same [Provider] P that - * the parent injector would have provided, that is, P is the [Provider] of - * the nearest ancestor of the parent injector that binds K. Note that this - * differs from how [get] finds P in that visibility is not taken into - * account. - * - * Thus, if a descendant D of the child requests an instance for K, the child - * will mask any binding for K made by a proper ancestor injector, provided - * that P's visibility reveals the child's binding to D. - * For example, if the child has no proper descendant and P's visibility - * deems that the child is visible to the child itself, then the first - * request for the child to get an instance for K will trigger the creation of - * a new instance. - * - * [name] is used for error reporting. */ - Injector createChild(List modules, - {List forceNewInstances, String name}); + @deprecated + Injector createChild(List modules); +} + + +/** + * The RootInjector serves as an alternative to having a null parent for + * injectors that have no parent. This allows us to bypass checking for a null + * parent, and instead RootInjector will start the exception chain that reports + * the resolution context tree when no providers are found. + */ +class RootInjector extends Injector { + Injector get parent => null; + List get _instances => null; + dynamic getByKey(key, [depth]) => throw new NoProviderError(key); + Injector createChild(m) => null; +} + +class ModuleInjector extends Injector { + + static final rootInjector = new RootInjector(); + final Injector parent; + + List _bindings; + List _instances; + + ModuleInjector(List modules, [Injector parent]) + : parent = parent == null ? rootInjector : parent, + _bindings = new List(Key.numInstances + 1), // + 1 for injector itself + _instances = new List.filled(Key.numInstances + 1, _Instance.EMPTY) { + + if (modules != null) { + modules.forEach((module) { + module.bindings.forEach((Key key, Binding binding) => + _bindings[key.id] = binding); + }); + } + _instances[_INJECTOR_KEY.id] = this; + } + + Iterable _typesCache; + + Iterable get _types { + if (_bindings == null) return []; + + if (_typesCache == null) { + _typesCache = _bindings + .where((p) => p != null) + .map((p) => p.key.type); + } + return _typesCache; + } + + Set get types { + var types = new Set(); + for (var node = this; node.parent != null; node = node.parent) { + types.addAll(node._types); + } + types.add(Injector); + return types; + } + + dynamic getByKey(Key key) { + var id = key.id; + if (id >= _instances.length) { + throw new NoProviderError(key); + } + var instance = _instances[id]; + if (identical(instance, _Instance.CREATING)) { + _instances[id] = _Instance.EMPTY; + throw new CircularDependencyError(key); + } + if (!identical(instance, _Instance.EMPTY)) return instance; + + Binding binding = _bindings[id]; + // When binding is null, recurse instead of iterate because it: + // 1. tracks key history on the stack for error reporting + // 2. allows different types of ancestor injectors with alternative implementations. + // An alternative could be to recurse only when parent is not a ModuleInjector + if (binding == null) return _instances[id] = parent.getByKey(key); + + _instances[id] = _Instance.CREATING; + try { + var paramKeys = binding.parameterKeys; + var length = paramKeys.length; + var factory = binding.factory; + + if (length > 15) { + var params = new List(length); + for (var i = 0; i < length; i++) { + params[i] = getByKey(paramKeys[i]); + } + return _instances[id] = Function.apply(factory, params); + } + + var a1 = length >= 1 ? getByKey(paramKeys[0]) : null; + var a2 = length >= 2 ? getByKey(paramKeys[1]) : null; + var a3 = length >= 3 ? getByKey(paramKeys[2]) : null; + var a4 = length >= 4 ? getByKey(paramKeys[3]) : null; + var a5 = length >= 5 ? getByKey(paramKeys[4]) : null; + var a6 = length >= 6 ? getByKey(paramKeys[5]) : null; + var a7 = length >= 7 ? getByKey(paramKeys[6]) : null; + var a8 = length >= 8 ? getByKey(paramKeys[7]) : null; + var a9 = length >= 9 ? getByKey(paramKeys[8]) : null; + var a10 = length >= 10 ? getByKey(paramKeys[9]) : null; + var a11 = length >= 11 ? getByKey(paramKeys[10]) : null; + var a12 = length >= 12 ? getByKey(paramKeys[11]) : null; + var a13 = length >= 13 ? getByKey(paramKeys[12]) : null; + var a14 = length >= 14 ? getByKey(paramKeys[13]) : null; + var a15 = length >= 15 ? getByKey(paramKeys[14]) : null; + + switch (length) { + case 0: return _instances[id] = factory(); + case 1: return _instances[id] = factory(a1); + case 2: return _instances[id] = factory(a1, a2); + case 3: return _instances[id] = factory(a1, a2, a3); + case 4: return _instances[id] = factory(a1, a2, a3, a4); + case 5: return _instances[id] = factory(a1, a2, a3, a4, a5); + case 6: return _instances[id] = factory(a1, a2, a3, a4, a5, a6); + case 7: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7); + case 8: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8); + case 9: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9); + case 10: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + case 11: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); + case 12: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); + case 13: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); + case 14: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); + case 15: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + } + } on ResolvingError catch (e) { + _instances[id] = _Instance.EMPTY; + e.appendKey(key); + rethrow; // to preserve stack trace + } catch (e) { + _instances[id] = _Instance.EMPTY; + rethrow; + } + } + + @deprecated + Injector createChild(List modules) { + return new ModuleInjector(modules,this); + } } diff --git a/lib/src/injector_delagate.dart b/lib/src/injector_delagate.dart deleted file mode 100644 index c05b3ca..0000000 --- a/lib/src/injector_delagate.dart +++ /dev/null @@ -1,63 +0,0 @@ -library di.injector_delegate; - -import 'base_injector.dart'; -import 'provider.dart'; -import 'package:di/di.dart'; - -class InjectorDelagate implements BaseInjector, ObjectFactory { - BaseInjector _injector; - ResolutionContext _resolving; - - InjectorDelagate(this._injector, this._resolving); - - @override - bool get allowImplicitInjection => _injector.allowImplicitInjection; - - @override - String get name => _injector.name; - - @override - Injector get root => _injector.root; - - @override - Set get types => _injector.types; - - @override - Injector get parent => _injector.parent; - - @override - dynamic get(Type type, [Type annotation]) => - _injector.getInstanceByKey(new Key(type, annotation), this, _resolving); - - @override - dynamic getInstanceByKey(Key key, Injector requester, ResolutionContext resolving) => - _injector.getInstanceByKey(key, requester, resolving); - - @override - dynamic getByKey(Key key) => - _injector.getInstanceByKey(key, this, _resolving); - - @override - Injector createChild(List modules, - {List forceNewInstances, String name}) => - _injector.createChildWithResolvingHistory(modules, _resolving, - forceNewInstances: forceNewInstances, - name: name); - - @override - newFromParent(List modules, String name) => - _injector.newFromParent(modules, name); - - @override - Object newInstanceOf(Type type, ObjectFactory factory, - Injector requestor, ResolutionContext resolving) => - _injector.newInstanceOf(type, factory, requestor, resolving); - - @override - Injector createChildWithResolvingHistory( - List modules, ResolutionContext resolving, - {List forceNewInstances, String name}) { - throw new UnsupportedError( - 'can\'t call createChildWithResolvingHistory on delegate'); - } -} diff --git a/lib/src/module.dart b/lib/src/module.dart index fbc5a0a..4f961f4 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -1,26 +1,56 @@ -part of di; +library di.module; -// TODO: Make this private in DI 2.0. It is made public to temporarily allow -// people create delegates to bind* methods with identical defaults. -@deprecated -const DEFAULT_VALUE = _DEFAULT_VALUE; +import "../key.dart"; +import "../check_bind_args.dart" show checkBindArgs; +import "reflector.dart"; +import "reflector_dynamic.dart"; +import "errors.dart" show PRIMITIVE_TYPES; -_DEFAULT_VALUE(_) => null; +DEFAULT_VALUE(_) => null; +IDENTITY(p) => p; -typedef dynamic FactoryFn(Injector injector); +class Binding { + Key key; + List parameterKeys; + Function factory; -/** - * If owned by a [Provider] P bound by the [defining] injector, then this - * returns whether P is visible to the [requesting] injector. - * See [Injector.get]. - */ -typedef bool Visibility(Injector requesting, Injector defining); + _checkPrimitive(Key key) { + if (PRIMITIVE_TYPES.contains(key)) { + throw "Cannot bind primitive type '${key.type}'."; + } + return true; + } -/** - * Produces an instance of some type, provided [factory] produces instances of - * the dependencies that type. - */ -typedef Object TypeFactory(factory(Type type, Type annotation)); + void bind(k, TypeReflector reflector, {toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, Type toImplementation, + List inject: const[]}) { + key = k; + assert(_checkPrimitive(k)); + if (inject.length == 1 && isNotSet(toFactory)) { + toFactory = IDENTITY; + } + assert(checkBindArgs(toValue, toFactory, toImplementation, inject)); + + if (isSet(toValue)) { + factory = () => toValue; + parameterKeys = const []; + } else if (isSet(toFactory)) { + factory = toFactory; + parameterKeys = inject.map((t) { + if (t is Key) return t; + if (t is Type) return new Key(t); + throw "inject must be Keys or Types. '$t' is not an instance of Key or Type."; + }).toList(growable: false); + } else { + var implementationType = toImplementation == null ? key.type : toImplementation; + parameterKeys = reflector.parameterKeysFor(implementationType); + factory = reflector.factoryFor(implementationType); + } + } +} + +bool isSet(val) => !identical(val, DEFAULT_VALUE); +bool isNotSet(val) => !isSet(val); /** * Module contributes configuration information to an [Injector] by providing @@ -31,32 +61,28 @@ typedef Object TypeFactory(factory(Type type, Type annotation)); * no effect on that injector. */ class Module { - final _providers = {}; - final _childModules = []; - Map _typeFactories = {}; - - Map get typeFactories { - if (_childModules.isEmpty) return _typeFactories; - - var factories = new Map.from(_typeFactories); - _childModules.forEach((m) { - if (m.typeFactories != null) { - factories.addAll(m.typeFactories); - } - }); - return factories; - } + static TypeReflector DEFAULT_REFLECTOR = getReflector(); - set typeFactories(Map factories) { - _typeFactories = factories; - } + /** + * A [TypeReflector] for the module to look up constructors for types when + * toFactory and toValue are not specified. This is done with mirroring or + * pre-generated typeFactories. + */ + final TypeReflector reflector; - void updateListWithBindings(List providers) { - _childModules.forEach((child) => child.updateListWithBindings(providers)); - _providers.forEach((k, v) { - providers[k] = v; - }); - } + Module(): reflector = DEFAULT_REFLECTOR; + + /** + * Use a custom reflector instead of the default. Useful for testing purposes. + */ + Module.withReflector(this.reflector); + + Map bindings = new Map(); + + /** + * Copies all bindings of [module] into this one. Overwriting when conflicts are found. + */ + void install(Module module) => bindings.addAll(module.bindings); /** * Registers a binding for a given [type]. @@ -67,127 +93,35 @@ class Module { * * * [toImplementation]: The given type will be instantiated using the [new] * operator and the resulting instance will be injected. - * * [toFactory]: The result of the factory function is the value that will - * be injected. + * * [toFactory]: The result of the factory function called with the types of [inject] as arguments + * is the value that will be injected. * * [toValue]: The given value will be injected. * * [withAnnotation]: Type decorated with additional annotation. - * * [visibility]: Function which determines if the requesting injector can - * see the type in the current injector. * * Up to one (0 or 1) of the following parameters can be specified at the * same time: [toImplementation], [toFactory], [toValue]. + * + * If [inject] is a single item list with no other parameters specified, the specified + * type will be instantiated using its current binding. This is shorthand for + * toFactory: (item) => item, inject: [item]. */ - void bind(Type type, {dynamic toValue: _DEFAULT_VALUE, - Function toFactory: _DEFAULT_VALUE, Type toImplementation, - Type withAnnotation, Visibility visibility, List inject}) { + void bind(Type type, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, Type toImplementation, + List inject: const [], Type withAnnotation}) { bindByKey(new Key(type, withAnnotation), toValue: toValue, - toFactory: toFactory, toImplementation: toImplementation, - visibility: visibility, inject: inject); + toFactory: toFactory, toImplementation: toImplementation, inject: inject); } /** * Same as [bind] except it takes [Key] instead of - * [Type] [withAnnotation] combination. + * [Type] [withAnnotation] combination. Faster. */ - void bindByKey(Key key, {dynamic toValue: _DEFAULT_VALUE, - Function toFactory: _DEFAULT_VALUE, Type toImplementation, - Visibility visibility, List inject}) { - _checkBindArgs(toValue, toFactory, toImplementation); - - if (inject != null) { - if (toFactory == _DEFAULT_VALUE) { - assert (inject.length == 1); - toFactory = (i) => i.get(inject[0]); - } else { - var originalFactory = toFactory; - toFactory = (Injector injector) { - var keys = inject.map((t) { - if (t is Type) return new Key(t); - if (t is Key) return t; - throw "inject must be a list of keys or types"; - }); - var params = keys.map(injector.getByKey).toList(); - return Function.apply(originalFactory, params); - }; - } - } + void bindByKey(Key key, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, List inject: const [], Type toImplementation}) { - if (!identical(toValue, _DEFAULT_VALUE)) { - _providers[key.id] = new ValueProvider(key.type, toValue, visibility); - } else if (!identical(toFactory, _DEFAULT_VALUE)) { - _providers[key.id] = new FactoryProvider(key.type, toFactory, visibility); - } else { - _providers[key.id] = new TypeProvider( - toImplementation == null ? key.type : toImplementation, visibility); - } - } - - _checkBindArgs(toValue, toFactory, toImplementation) { - int count = 0; - if (!identical(toValue, _DEFAULT_VALUE)) count++; - if (!identical(toFactory, _DEFAULT_VALUE)) count++; - if (toImplementation != null) count++; - if (count > 1) { - throw 'Only one of following parameters can be specified: ' - 'toValue, toFactory, toImplementation'; - } - return true; - } - - /** - * Register a binding to a concrete value. - * - * The [value] is what actually will be injected. - */ - @Deprecated("Use bind(type, toValue: value)") - void value(Type id, value, {Type withAnnotation, Visibility visibility}) { - bind(id, toValue: value, withAnnotation: withAnnotation, - visibility: visibility); - } - - /** - * Registers a binding for a [Type]. - * - * The default behavior is to simply instantiate the type. - * - * The following parameters can be specified: - * - * * [withAnnotation]: Type decorated with additional annotation. - * * [implementedBy]: The type will be instantiated using the [new] operator - * and the resulting instance will be injected. If no type is provided, - * then it's implied that [type] should be instantiated. - * * [visibility]: Function which determines fi the requesting injector can - * see the type in the current injector. - */ - @Deprecated("Use bind(type, implementedBy: impl)") - void type(Type type, {Type withAnnotation, Type implementedBy, Visibility visibility}) { - bind(type, withAnnotation: withAnnotation, visibility: visibility, - toImplementation: implementedBy); - } - - /** - * Register a binding to a factory function. - * - * The [factoryFn] will be called and the result of that function is the value - * that will be injected. - */ - @Deprecated("Use bind(type, toFactory: factory)") - void factory(Type id, FactoryFn factoryFn, {Type withAnnotation, - Visibility visibility}) { - bind(id, withAnnotation: withAnnotation, visibility: visibility, - toFactory: factoryFn); - } - - @Deprecated("Use bindByKey(type, toFactory: factory)") - void factoryByKey(Key key, FactoryFn factoryFn, {Visibility visibility}) { - bindByKey(key, visibility: visibility, toFactory: factoryFn); - } - - /** - * Installs another module into this module. Bindings defined on this module - * take precedence over the installed module. - */ - void install(Module module) { - _childModules.add(module); + var binding = new Binding(); + binding.bind(key, reflector, toValue: toValue, toFactory: toFactory, + toImplementation: toImplementation, inject: inject); + bindings[key] = binding; } } diff --git a/lib/src/provider.dart b/lib/src/provider.dart deleted file mode 100644 index 662f197..0000000 --- a/lib/src/provider.dart +++ /dev/null @@ -1,51 +0,0 @@ -library di.provider; - -import 'injector_delagate.dart'; -import 'base_injector.dart'; -import 'package:di/di.dart'; - -abstract class ObjectFactory { - Object getInstanceByKey(Key key, BaseInjector requester, ResolutionContext resolving); -} - -abstract class Provider { - final Visibility visibility; - final Type type; - - Provider(this.type, this.visibility); - - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving); -} - -class ValueProvider extends Provider { - dynamic value; - - ValueProvider(type, this.value, [Visibility visibility]) - : super(type, visibility); - - @override - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving) => value; -} - -class TypeProvider extends Provider { - TypeProvider(type, [Visibility visibility]) : super(type, visibility); - - @override - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving) => - injector.newInstanceOf(type, objFactory, requestor, resolving); -} - -class FactoryProvider extends Provider { - final Function factoryFn; - - FactoryProvider(type, this.factoryFn, [Visibility visibility]) - : super(type, visibility); - - @override - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving) => - factoryFn(new InjectorDelagate(injector, resolving)); -} diff --git a/lib/src/reflector.dart b/lib/src/reflector.dart new file mode 100644 index 0000000..9de6a73 --- /dev/null +++ b/lib/src/reflector.dart @@ -0,0 +1,34 @@ +library di.reflector; + +import "../key.dart"; +import "errors.dart"; +import "module.dart"; + +abstract class TypeReflector { + /** + * Returns a factory function that knows how to construct an instance of a type. + * + * This interface is type based because there is only one factory for each + * type, no matter what the annotations are. However, the parameters returned + * are keys because annotations matter in that case so the injector knows + * what to inject. This leads to some performance loss from type comparison + * and key creation in DynamicTypeFactories but TypeReflector should only be + * used during module binding. + */ + Function factoryFor(Type type); + + /** + * Returns keys of the items that must be injected into the corresponding + * Factory that TypeReflector.factoryFor returns. + */ + List parameterKeysFor(Type type); + + /** + * Adds these factories and parameterKeys to the reflector, so that future calls + * to factoryFor and parameterKeysFor will return these new values. + * + * Overwrites in static implementation, no-op in dynamic implementation + */ + void addAll(Map factories, Map> parameterKeys); + void add(Type type, Function factory, List parameterKeys); +} diff --git a/lib/src/reflector_dynamic.dart b/lib/src/reflector_dynamic.dart new file mode 100644 index 0000000..550f64e --- /dev/null +++ b/lib/src/reflector_dynamic.dart @@ -0,0 +1,235 @@ +library di.reflector_dynamic; + +import '../di.dart'; +import 'mirrors.dart'; + +TypeReflector getReflector() => new DynamicTypeFactories(); + +class DynamicTypeFactories extends TypeReflector { + /// caches of results calculated from mirroring + final List _factories = new List(); + final List> _parameterKeys = new List>(); + static List lists = new List.generate(26, (i) => new List(i)); + + Function factoryFor(Type type) { + var key = new Key(type); + _resize(key.id); + Function factory = _factories[key.id]; + if (factory == null) { + factory = _factories[key.id] = _generateFactory(type); + } + return factory; + } + + List parameterKeysFor(Type type) { + var key = new Key(type); + _resize(key.id); + List parameterKeys = _parameterKeys[key.id]; + if (parameterKeys == null) { + parameterKeys = _parameterKeys[key.id] = _generateParameterKeys(type); + } + return parameterKeys; + } + + void _resize(int maxId) { + if (_factories.length <= maxId) { + _factories.length = maxId + 1; + _parameterKeys.length = maxId + 1; + } + } + + Function _generateFactory(Type type) { + ClassMirror classMirror = _reflectClass(type); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + int length = ctor.parameters.length; + Function create = classMirror.newInstance; + Symbol name = ctor.constructorName; + Function factory; + if (length > 25) throw "Too many arguments in $name constructor for dynamic DI to handle :("; + List l = lists[length]; + // script for this is in scripts/reflector_dynamic_script.dart + switch (length) { + case 0: + return () { + return create(name, l).reflectee; + }; + case 1: + return (a1) { + l[0]=a1; + return create(name, l).reflectee; + }; + case 2: + return (a1, a2) { + l[0]=a1;l[1]=a2; + return create(name, l).reflectee; + }; + case 3: + return (a1, a2, a3) { + l[0]=a1;l[1]=a2;l[2]=a3; + return create(name, l).reflectee; + }; + case 4: + return (a1, a2, a3, a4) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4; + return create(name, l).reflectee; + }; + case 5: + return (a1, a2, a3, a4, a5) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5; + return create(name, l).reflectee; + }; + case 6: + return (a1, a2, a3, a4, a5, a6) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6; + return create(name, l).reflectee; + }; + case 7: + return (a1, a2, a3, a4, a5, a6, a7) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7; + return create(name, l).reflectee; + }; + case 8: + return (a1, a2, a3, a4, a5, a6, a7, a8) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8; + return create(name, l).reflectee; + }; + case 9: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9; + return create(name, l).reflectee; + }; + case 10: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10; + return create(name, l).reflectee; + }; + case 11: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11; + return create(name, l).reflectee; + }; + case 12: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12; + return create(name, l).reflectee; + }; + case 13: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13; + return create(name, l).reflectee; + }; + case 14: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14; + return create(name, l).reflectee; + }; + case 15: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15; + return create(name, l).reflectee; + }; + case 16: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16; + return create(name, l).reflectee; + }; + case 17: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17; + return create(name, l).reflectee; + }; + case 18: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18; + return create(name, l).reflectee; + }; + case 19: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19; + return create(name, l).reflectee; + }; + case 20: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20; + return create(name, l).reflectee; + }; + case 21: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21; + return create(name, l).reflectee; + }; + case 22: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22; + return create(name, l).reflectee; + }; + case 23: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23; + return create(name, l).reflectee; + }; + case 24: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23;l[23]=a24; + return create(name, l).reflectee; + }; + case 25: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23;l[23]=a24;l[24]=a25; + return create(name, l).reflectee; + }; + } + } + + List _generateParameterKeys(Type type) { + ClassMirror classMirror = _reflectClass(type); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + + return new List.generate(ctor.parameters.length, (int pos) { + ParameterMirror p = ctor.parameters[pos]; + if (p.type.qualifiedName == #dynamic) { + var name = MirrorSystem.getName(p.simpleName); + throw new DynamicReflectorError("Error getting params for '$type': " + "The '$name' parameter must be typed"); + } + if (p.type is TypedefMirror) { + throw new DynamicReflectorError("Typedef '${p.type}' in constructor " + "'${classMirror.simpleName}' is not supported."); + } + if (p.metadata.length > 1) { + throw new DynamicReflectorError( + "Constructor '${classMirror.simpleName}' parameter $pos of type " + "'${p.type}' can have only zero on one annotation, but it has " + "'${p.metadata}'."); + } + ClassMirror pTypeMirror = (p.type as ClassMirror); + var pType = pTypeMirror.reflectedType; + if (pTypeMirror.typeArguments.where((m) => m.qualifiedName != #dynamic).isNotEmpty) { + throw new DynamicReflectorError("$pType cannot be injected because it is parameterized " + "with non-generic types."); + } + var annotationType = p.metadata.isNotEmpty ? p.metadata.first.type.reflectedType : null; + return new Key(pType, annotationType); + }, growable:false); + } + + ClassMirror _reflectClass(Type type) { + ClassMirror classMirror = reflectType(type); + if (classMirror is TypedefMirror) { + throw new DynamicReflectorError("No implementation provided for " + "${getSymbolName(classMirror.qualifiedName)} typedef!"); + } + + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + + if (ctor == null) { + throw new DynamicReflectorError("Unable to find default constructor for $type. " + "Make sure class has a default constructor." + (1.0 is int ? + "Make sure you have correctly configured @MirrorsUsed." : "")); + } + return classMirror; + } + + void addAll(Map factories, Map> parameterKeys) => null; + void add(Type type, Function factory, List parameterKeys) => null; +} diff --git a/lib/src/reflector_null.dart b/lib/src/reflector_null.dart new file mode 100644 index 0000000..43b93c7 --- /dev/null +++ b/lib/src/reflector_null.dart @@ -0,0 +1,22 @@ +library di.reflector_null; + +import '../key.dart'; +import 'reflector.dart'; +import 'errors.dart'; + +TypeReflector getReflector() => new NullReflector(); + +class NullReflector extends TypeReflector { + factoryFor(Type type) => throw new NullReflectorError(); + parameterKeysFor(Type type) => throw new NullReflectorError(); + addAll(Map factories, Map> parameterKeys) => + throw new NullReflectorError(); + add(Type type, Function factory, List parameterKeys) => + throw new NullReflectorError(); +} + +class NullReflectorError extends BaseError { + NullReflectorError() + : super("Module.DEFAULT_REFLECTOR not initialized for dependency injection." + "http://goo.gl/XFXx9G"); +} diff --git a/lib/src/reflector_static.dart b/lib/src/reflector_static.dart new file mode 100644 index 0000000..4b968fe --- /dev/null +++ b/lib/src/reflector_static.dart @@ -0,0 +1,38 @@ +library di.reflector_static; + +import '../di.dart'; + +class GeneratedTypeFactories extends TypeReflector { + + Map _factories; + Map> _parameterKeys; + + /** + * This constructor should be called by code generated by transformer in main + * with generated factories and paramKeys to initialize it. + */ + GeneratedTypeFactories(Map this._factories, + Map>this._parameterKeys); + + Function factoryFor(Type type) { + var keys = _factories[type]; + if (keys != null) return keys; + throw new NoGeneratedTypeFactoryError(type); + } + + List parameterKeysFor(Type type) { + var keys = _parameterKeys[type]; + if (keys != null) return keys; + throw new NoGeneratedTypeFactoryError(type); + } + + void addAll(Map factories, Map> parameterKeys) { + _factories.addAll(factories); + _parameterKeys.addAll(parameterKeys); + } + + void add(Type type, Function factory, List paramKeys) { + _factories[type] = factory; + _parameterKeys[type] = paramKeys; + } +} diff --git a/lib/static_injector.dart b/lib/static_injector.dart deleted file mode 100644 index fe0c6f3..0000000 --- a/lib/static_injector.dart +++ /dev/null @@ -1,64 +0,0 @@ -library di.static_injector; - -import 'di.dart'; -import 'src/error_helper.dart'; -import 'src/base_injector.dart'; -import 'src/provider.dart'; - -export 'annotations.dart'; -export 'di.dart'; - -/** - * Static implementation of [Injector] that uses type factories - */ -class StaticInjector extends BaseInjector { - Map typeFactories; - - StaticInjector({List modules, String name, - bool allowImplicitInjection: false, typeFactories}) - : super(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection) { - this.typeFactories = _extractTypeFactories(modules, typeFactories); - } - - StaticInjector._fromParent(List modules, Injector parent, {name}) - : super.fromParent(modules, parent, name: name) { - this.typeFactories = _extractTypeFactories(modules); - } - - newFromParent(List modules, String name) => - new StaticInjector._fromParent(modules, this, name: name); - - Object newInstanceOf(Type type, ObjectFactory objFactory, - Injector requestor, ResolutionContext resolving) { - TypeFactory typeFactory = _getFactory(type); - if (typeFactory == null) { - throw new NoProviderError( - error(resolving, 'No type factory provided for $type!')); - } - return typeFactory((type, [annotation]) => - objFactory.getInstanceByKey( - new Key(type, annotation), requestor, resolving)); - } - - TypeFactory _getFactory(Type key) { - var cursor = this; - while (cursor != null) { - if (cursor.typeFactories.containsKey(key)) { - return cursor.typeFactories[key]; - } - cursor = cursor.parent; - } - return null; - } -} - -Map _extractTypeFactories(List modules, - [Map initial = const {}]) { - if (modules == null || modules.isEmpty) return initial; - var factories = new Map.from(initial == null ? {} : initial); - modules.forEach((m) { - factories.addAll(m.typeFactories); - }); - return factories; -} diff --git a/lib/transformer.dart b/lib/transformer.dart index 0e74ccd..08ed896 100644 --- a/lib/transformer.dart +++ b/lib/transformer.dart @@ -1,5 +1,23 @@ /** - * Transformer which generates type factories for static injection. + * Static injection transformer which generates, for each injectable type: + * + * - typeFactory: which is a closure (a1, a2...) => new Type(a1, a2...) where + * args are injected dependency instances, as specified by + * - paramKeys: List corresponding to the dependency needing to be injected + * in the positional arguments of the typeFactory. + * + * These two give an injector the information needed to construct an instance of a + * type without using mirrors. They are stored as a Map + * and outputted to a file [entry_point_name]_generated_type_factory_maps.dart. Multiple + * entry points (main functions) is not supported. + * + * The import in main is also modified to use import di_static.dart instead + * of di.dart, and imports module_dynamic.dart. + * + * All of the above is taken care of by the transformer. The user needs to call + * setupModuleTypeReflector in main, before any modules are initialized. User must also + * annotate types for the transformer to add them to the generated type factories file, + * in addition to enabling the transformer in pubspec.yaml. * * Types which are considered injectable can be annotated in the following ways: * @@ -53,10 +71,12 @@ library di.transformer; import 'dart:io'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; -import 'package:di/transformer/injector_generator.dart'; -import 'package:di/transformer/options.dart'; import 'package:path/path.dart' as path; +import 'transformer/injector_generator.dart'; +import 'transformer/options.dart'; +export 'transformer/options.dart'; +export 'transformer/injector_generator.dart'; /** * The transformer, which will extract all classes being dependency injected @@ -120,5 +140,7 @@ _readStringListValue(Map args, String name) { return results; } -List> _createPhases(TransformOptions options) => - [[new InjectorGenerator(options, new Resolvers(options.sdkDirectory))]]; +List> _createPhases(TransformOptions options) { + var resolvers = new Resolvers(options.sdkDirectory); + return [[new InjectorGenerator(options, resolvers)]]; +} diff --git a/lib/transformer/injector_generator.dart b/lib/transformer/injector_generator.dart index a6af126..a524fa7 100644 --- a/lib/transformer/injector_generator.dart +++ b/lib/transformer/injector_generator.dart @@ -5,10 +5,8 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/element.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; -import 'package:di/transformer/options.dart'; import 'package:path/path.dart' as path; - -import 'refactor.dart'; +import 'options.dart'; /** * Pub transformer which generates type factories for all injectable types @@ -61,21 +59,18 @@ class _Processor { var id = transform.primaryInput.id; var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' - '_static_injector.dart'; + '_generated_type_factory_maps.dart'; var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); _generatedAssetId = new AssetId(id.package, outputPath); var constructors = _gatherConstructors(); + // generates typeFactory file var injectLibContents = _generateInjectLibrary(constructors); transform.addOutput( new Asset.fromString(_generatedAssetId, injectLibContents)); - transformIdentifiers(transform, resolver, - identifier: 'di.auto_injector.defaultInjector', - replacement: 'createStaticInjector', - importPrefix: 'generated_static_injector', - importUrl: outputFilename); + _editMain(); } /** Resolves the classes for the injectable annotations in the current AST. */ @@ -184,7 +179,7 @@ class _Processor { } /** - * Checks if the element is annotated with one of the known injectablee + * Checks if the element is annotated with one of the known injectable * annotations. */ bool _isElementAnnotated(Element e) { @@ -306,36 +301,56 @@ class _Processor { return '$prefix${type.name}'; } + var keysBuffer = new StringBuffer(); var factoriesBuffer = new StringBuffer(); + var paramsBuffer = new StringBuffer(); + var addedKeys = new Set(); for (var ctor in constructors) { var type = ctor.enclosingElement; var typeName = resolveClassName(type); - factoriesBuffer.write(' $typeName: (f) => new $typeName('); + + String args = new List.generate(ctor.parameters.length, (i) => 'a${i+1}').join(', '); + factoriesBuffer.write(' $typeName: ($args) => new $typeName($args),\n'); + + paramsBuffer.write(' $typeName: '); + paramsBuffer.write(ctor.parameters.isEmpty ? 'const[' : '['); var params = ctor.parameters.map((param) { var typeName = resolveClassName(param.type.element); - var annotations = []; + Iterable annotations = []; if (param.metadata.isNotEmpty) { annotations = param.metadata.map( - (item) => resolveClassName(item.element.returnType.element)); + (item) => item.element.returnType.element); + } + + var keyName = '_KEY_${param.type.name}' + + (annotations.isNotEmpty ? '_${annotations.first}' : ''); + if (addedKeys.add(keyName)) { + keysBuffer.writeln('final Key $keyName = new Key($typeName' + + (annotations.isNotEmpty ? ', ${resolveClassName(annotations.first)});' : ');')); } - var annotationsSuffix = - annotations.isNotEmpty ? ', ${annotations.first}' : ''; - return 'f($typeName$annotationsSuffix)'; + return keyName; }); - factoriesBuffer.write('${params.join(', ')}),\n'); + paramsBuffer.write('${params.join(', ')}],\n'); } var outputBuffer = new StringBuffer(); - _writeStaticInjectorHeader(transform.primaryInput.id, outputBuffer); + _writeHeader(transform.primaryInput.id, outputBuffer); usedLibs.forEach((lib) { if (lib.isDartCore) return; var uri = resolver.getImportUri(lib, from: _generatedAssetId); outputBuffer.write('import \'$uri\' as ${prefixes[lib]};\n'); }); - _writePreamble(outputBuffer); - outputBuffer.write(factoriesBuffer); - _writeFooter(outputBuffer); + outputBuffer..write('\n') + ..write(keysBuffer) + ..write('final Map typeFactories = {\n') + ..write(factoriesBuffer) + ..write('};\nfinal Map> parameterKeys = {\n') + ..write(paramsBuffer) + ..write('};\n') + ..write('setStaticReflectorAsDefault() => ' + 'Module.DEFAULT_REFLECTOR = ' + 'new GeneratedTypeFactories(typeFactories, parameterKeys);\n'); return outputBuffer.toString(); } @@ -344,34 +359,48 @@ class _Processor { logger.warning(msg, asset: resolver.getSourceAssetId(element), span: resolver.getSourceSpan(element)); } + + /// import generated_type_factory_maps and call initialize + void _editMain() { + AssetId id = transform.primaryInput.id; + var lib = resolver.getLibrary(id); + var unit = lib.definingCompilationUnit.node; + var transaction = resolver.createTextEditTransaction(lib); + + var imports = unit.directives.where((d) => d is ImportDirective); + transaction.edit(imports.last.end, imports.last.end, '\nimport ' + "'${path.url.basenameWithoutExtension(id.path)}" + "_generated_type_factory_maps.dart' show setStaticReflectorAsDefault;"); + + FunctionExpression main = unit.declarations.where((d) => + d is FunctionDeclaration && d.name.toString() == 'main') + .first.functionExpression; + var body = main.body; + if (body is BlockFunctionBody) { + var location = body.beginToken.end; + transaction.edit(location, location, '\n setStaticReflectorAsDefault();'); + } else if (body is ExpressionFunctionBody) { + transaction.edit(body.beginToken.offset, body.endToken.end, + "{\n setStaticReflectorAsDefault();\n" + " return ${body.expression};\n}"); + } // EmptyFunctionBody can only appear as abstract methods and constructors. + + var printer = transaction.commit(); + var url = id.path.startsWith('lib/') ? + 'package:${id.package}/${id.path.substring(4)}' : id.path; + printer.build(url); + transform.addOutput(new Asset.fromString(id, printer.text)); + } } -void _writeStaticInjectorHeader(AssetId id, StringSink sink) { +void _writeHeader(AssetId id, StringSink sink) { var libName = path.withoutExtension(id.path).replaceAll('/', '.'); libName = libName.replaceAll('-', '_'); sink.write(''' -library ${id.package}.$libName.generated_static_injector; +library ${id.package}.$libName.generated_type_factory_maps; import 'package:di/di.dart'; -import 'package:di/static_injector.dart'; - -'''); -} - -void _writePreamble(StringSink sink) { - sink.write(''' -Injector createStaticInjector({List modules, String name, - bool allowImplicitInjection: false}) => - new StaticInjector(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection, - typeFactories: factories); +import 'package:di/src/reflector_static.dart'; -final Map factories = { -'''); -} - -void _writeFooter(StringSink sink) { - sink.write(''' -}; '''); } diff --git a/lib/transformer/options.dart b/lib/transformer/options.dart index a9d5b17..31cbbed 100644 --- a/lib/transformer/options.dart +++ b/lib/transformer/options.dart @@ -1,7 +1,6 @@ library di.transformer.options; import 'dart:async'; - import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; @@ -36,16 +35,16 @@ class TransformOptions { final String sdkDirectory; TransformOptions({EntryFilter entryFilter, String sdkDirectory, - List injectableAnnotations, List injectedTypes}) - : entryFilter = entryFilter != null ? entryFilter : isPossibleDartEntry, - sdkDirectory = sdkDirectory, - injectableAnnotations = - (injectableAnnotations != null ? injectableAnnotations : []) - ..addAll(DEFAULT_INJECTABLE_ANNOTATIONS), - injectedTypes = - new Set.from(injectedTypes != null ? injectedTypes : []) { - if (sdkDirectory == null) - throw new ArgumentError('sdkDirectory must be provided.'); + List injectableAnnotations, List injectedTypes}) + : entryFilter = entryFilter != null ? entryFilter : isPossibleDartEntry, + sdkDirectory = sdkDirectory, + injectableAnnotations = + (injectableAnnotations != null ? injectableAnnotations : []) + ..addAll(DEFAULT_INJECTABLE_ANNOTATIONS), + injectedTypes = + new Set.from(injectedTypes != null ? injectedTypes : []) { + if (sdkDirectory == null) + throw new ArgumentError('sdkDirectory must be provided.'); } Future isDartEntry(Asset asset) => new Future.value(entryFilter(asset)); diff --git a/lib/transformer/refactor.dart b/lib/transformer/refactor.dart deleted file mode 100644 index 8d5d70e..0000000 --- a/lib/transformer/refactor.dart +++ /dev/null @@ -1,136 +0,0 @@ -library di.transformers.refactor; - -import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/element.dart'; -import 'package:barback/barback.dart'; -import 'package:code_transformers/resolver.dart'; -import 'package:source_maps/refactor.dart'; - - -/// Transforms all simple identifiers of [identifier] to be -/// [importPrefix].[replacement] in the entry point of the application. -/// -/// When the identifier is replaced, this function also adds a prefixed import -/// of the form `import "[importUrl]" as [importPrefix]`. -/// -/// This will resolve the full name of [identifier] and warn if it cannot be -/// resolved. This will only modify the main entry point of the application and -/// will not traverse into parts. -void transformIdentifiers(Transform transform, Resolver resolver, - {String identifier, String replacement, String importUrl, - String importPrefix}) { - - var identifierElement = resolver.getLibraryVariable(identifier); - if (identifierElement != null) { - identifierElement = identifierElement.getter; - } else { - identifierElement = resolver.getLibraryFunction(identifier); - } - - if (identifierElement == null) { - // TODO(blois) enable once on barback 0.12.0 - // transform.logger.fine('Unable to resolve $identifier, not ' - // 'transforming entry point.'); - transform.addOutput(transform.primaryInput); - return; - } - - var lib = resolver.getLibrary(transform.primaryInput.id); - var transaction = resolver.createTextEditTransaction(lib); - var unit = lib.definingCompilationUnit.node; - - unit.accept(new _IdentifierTransformer(transaction, identifierElement, - '$importPrefix.$replacement', transform.logger)); - - if (transaction.hasEdits) { - _addImport(transaction, unit, importUrl, importPrefix); - } - _commitTransaction(transaction, transform); -} - -/// Commits the transaction if there have been edits, otherwise just adds -/// the input as an output. -void _commitTransaction(TextEditTransaction transaction, Transform transform) { - var id = transform.primaryInput.id; - - if (transaction.hasEdits) { - var printer = transaction.commit(); - var url = id.path.startsWith('lib/') - ? 'package:${id.package}/${id.path.substring(4)}' : id.path; - printer.build(url); - transform.addOutput(new Asset.fromString(id, printer.text)); - } else { - // No modifications, so just pass the source through. - transform.addOutput(transform.primaryInput); - } -} - -/// Injects an import into the list of imports in the file. -void _addImport(TextEditTransaction transaction, CompilationUnit unit, - String uri, String prefix) { - var libDirective; - for (var directive in unit.directives) { - if (directive is ImportDirective) { - transaction.edit(directive.keyword.offset, directive.keyword.offset, - 'import \'$uri\' as $prefix;\n'); - return; - } else if (directive is LibraryDirective) { - libDirective = directive; - } - } - - // No imports, add after the library directive if there was one. - if (libDirective != null) { - transaction.edit(libDirective.endToken.offset + 2, - libDirective.endToken.offset + 2, - 'import \'$uri\' as $prefix;\n'); - } -} - -/// Vistior which changes every reference to a resolved element to a specific -/// string value. -class _IdentifierTransformer extends GeneralizingAstVisitor { - final TextEditTransaction transaction; - /// The element which all references to should be replaced. - final Element original; - /// The text which should replace [original]. - final String replacement; - /// The current logger. - final TransformLogger logger; - - _IdentifierTransformer(this.transaction, this.original, this.replacement, - this.logger); - - visitIdentifier(Identifier node) { - if (node.bestElement == original) { - transaction.edit(node.beginToken.offset, node.endToken.end, replacement); - return; - } - - super.visitIdentifier(node); - } - - // Top-level methods are not treated as prefixed identifiers, so handle those - // here. - visitMethodInvocation(MethodInvocation m) { - if (m.methodName.bestElement == original) { - if (m.target is SimpleIdentifier) { - // Include the prefix in the rename. - transaction.edit(m.target.beginToken.offset, m.methodName.endToken.end, - replacement); - } else { - transaction.edit(m.methodName.beginToken.offset, - m.methodName.endToken.end, replacement); - } - return; - } - super.visitMethodInvocation(m); - } - - // Skip the contents of imports/exports/parts - visitUriBasedDirective(ImportDirective d) {} - - visitPartDirective(PartDirective node) { - logger.warning('Not transforming code within ${node.uri}.'); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 789d0bf..6490278 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,22 @@ name: di -version: 1.2.3 +version: 2.0.0-alpha.11 authors: - Vojta Jina - Pavel Jbanov +- Anting Shen +- Misko Hevery description: Dependency Injection framework homepage: https://github.com/angular/di.dart environment: - sdk: '>=0.8.10' + sdk: '>=1.0.0' dependencies: - analyzer: '>=0.13.0 <0.14.0' - barback: '>=0.13.0 <0.14.0' - code_transformers: '>=0.1.3 <0.2.0' + analyzer: '>=0.15.0 <0.19.0' + barback: '>=0.13.0 <0.15.0' + code_transformers: '>=0.1.4+2 <0.2.0' path: ">=1.2.0 <2.0.0" dev_dependencies: benchmark_harness: ">=1.0.4 <2.0.0" browser: ">=0.10.0 <0.11.0" - guinness: ">=0.1.7 <0.2.0" - + guinness: ">=0.1.10 <0.2.0" transformers: -- di: - injectable_annotations: di.tests.InjectableTest +- di/module_transformer diff --git a/run-benchmarks.sh b/run-benchmarks.sh index 9dda711..2525f7a 100755 --- a/run-benchmarks.sh +++ b/run-benchmarks.sh @@ -4,8 +4,11 @@ set -e BENCHMARKS="module_benchmark.dart dynamic_injector_benchmark.dart static_injector_benchmark.dart -instance_benchmark.dart" +instance_benchmark.dart +large_benchmark.dart" +mkdir -p benchmark/generated_files +dart scripts/class_gen.dart # run tests in dart for b in $BENCHMARKS @@ -15,9 +18,10 @@ done # run dart2js on tests mkdir -p out +echo "running dart2js" for b in $BENCHMARKS do - dart2js --minify benchmark/$b -o out/$b.js + dart2js --minify benchmark/$b -o out/$b.js > /dev/null done # run tests in node diff --git a/run-tests.sh b/run-tests.sh index 203f5d0..0e3744d 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,16 +1,46 @@ - #!/bin/sh set -e -echo "run type factories generator for tests" +echo "Running generator..." ./test_tf_gen.sh -echo "run tests in dart" +echo "Running tests in Dart..." dart --checked test/main.dart -dart --checked test/generator_test.dart -dart --checked test/injector_generator_spec.dart - -echo "run dart2js on tests" +dart --checked test/transformer_test.dart + +# Example app test +echo "Building example..." +rm -rf example/build/ +cd example +pub_out=$(pub build | tee /dev/tty | grep -F "mirror" || : ) +cd .. +echo "--------" + +if [[ -n $pub_out ]] +then + echo "FAIL: Example transformer should not import dart:mirror" + failed=1 +else + echo "PASS: Example transformer should not import dart:mirror" +fi + +output=$(node example/build/web/main.dart.js || : ) +if [ $output == "Success" ] +then + echo "PASS: Example transformer should inject dependencies" +else + echo "FAIL: Example transformer should inject dependencies" + failed=1 +fi + +if [[ -n $failed ]] +then + echo "Tests failed. Build /example with \`pub build example/ --mode debug\` to debug." + exit 1 +fi +echo "" + +echo "Compiling tests to JavaScript with dart2js..." mkdir -p out dart2js --minify -c test/main.dart -o out/main.dart.js @@ -22,17 +52,4 @@ cat out/main.dart.js >> out/main.js echo "Running compiled tests in node..." node out/main.js -echo "run transformer tests" -pub build --mode=debug test - -echo "running transformer test (uncompiled, Dynamic DI)" -dart --checked test/auto_injector_test.dart - -echo "running transformer test (Static DI, Dart VM)" -dart --checked build/test/auto_injector_test.dart - -echo "running transformer test (Static DI, dart2js)" -# TODO(blois) dart2js compilation is not picking up transformed files, so -# run dart2js manually. dartbug.com/17198 -dart2js -c build/test/auto_injector_test.dart -o build/test/auto_injector_test.dart.js; -node build/test/auto_injector_test.dart.js +echo "Testing complete." diff --git a/scripts/check_bind_args_script.dart b/scripts/check_bind_args_script.dart new file mode 100644 index 0000000..4ef2b41 --- /dev/null +++ b/scripts/check_bind_args_script.dart @@ -0,0 +1,19 @@ +main() { + var s = new StringBuffer(); + + int max_arg_count = 15; + + for (var i = 0; i <= max_arg_count; i++) { + s.write("typedef _$i("); + s.write(new List.generate(i, (c) => "a${c+1}").join(", ")); + s.write(");\n"); + } + + s.write("switch (len) {\n"); + for (var i = 0; i <= max_arg_count; i++) { + s.write("case $i: argCountMatch = toFactory is _$i; break;\n"); + } + s.write('}'); + + print(s); +} diff --git a/scripts/class_gen.dart b/scripts/class_gen.dart new file mode 100644 index 0000000..a672901 --- /dev/null +++ b/scripts/class_gen.dart @@ -0,0 +1,45 @@ +/** + * This script generates a large number of classes and their corresponding factories + * to benchmark/generated_files, to be imported by benchmarks that require many classes + * and factories. Called from run-benchmarks.sh + */ +import 'dart:io'; +import 'dart:async'; + +main() { + int numClasses = 1000; + + File file = new File('benchmark/generated_files/classes.dart'); + var sink = file.openWrite(mode: WRITE); + sink.write('int c=0;\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('class Test$i{Test$i(){c++;}}\n'); + } + sink.close(); + + file = new File('benchmark/generated_files/factories.dart');; + sink = file.openWrite(mode: WRITE); + sink.write('import "package:di/key.dart";\n'); + sink.write('import "package:di/di.dart";\n'); + sink.write('import "classes.dart";\n'); + sink.write('export "classes.dart";\n'); + for (var i = 0; i< numClasses; i++) { + sink.write('final Key key$i = new Key(Test$i);\n'); + } + sink.write('List allKeys = [\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('key$i, '); + } + sink.write('];\n'); + sink.write('Map typeFactories = {\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('Test$i: () => new Test$i(),\n'); + } + sink.write('};\n'); + sink.write('Map> parameterKeys = {\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('Test$i: const[], '); + } + sink.write('\n};\n'); + sink.close(); +} diff --git a/scripts/reflector_dynamic_script.dart b/scripts/reflector_dynamic_script.dart new file mode 100644 index 0000000..e1477c5 --- /dev/null +++ b/scripts/reflector_dynamic_script.dart @@ -0,0 +1,15 @@ +main() { + var args; + for (var i = 0; i <= 25; i++) { + print("case $i:"); + args = new List.generate(i, (j) => "a${j+1}").join(', '); + print("return ($args) {"); + var buffer = new StringBuffer(); + for (var j = 0; j < i; j++){ + buffer.write("l[$j]=a${j+1};"); + } + print(buffer); + print("return create(name, l).reflectee;"); + print("};"); + } +} diff --git a/test/auto_injector_test.dart b/test/auto_injector_test.dart deleted file mode 100644 index ce86d62..0000000 --- a/test/auto_injector_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -library auto_injector_test; - -import 'package:di/auto_injector.dart' as auto; -import 'main.dart'; - - -void main() { - createInjectorSpec('Injector', - (modules, [name]) => auto.defaultInjector(modules: modules, name: name)); -} diff --git a/test/generator_test.dart b/test/generator_test.dart deleted file mode 100644 index 3b90f4e..0000000 --- a/test/generator_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:io'; -import 'package:analyzer/analyzer.dart'; -import 'package:di/generator.dart' as generator; -import 'package:guinness/guinness.dart'; -import 'package:matcher/matcher.dart' as matcher; - -main(args) { - - describe('generator', () { - - it('should codesplit deferred libraries', () { - Map code = generator.generateCode( - 'test_assets/gen_test1/main.dart', ['annotations.InjectableTest'], - Platform.environment['DART_SDK'], [Platform.packageRoot], 'main.dart'); - - expect(code.keys.map((chunk) => chunk.library == null ? null : chunk.library.name)) - .to(matcher.unorderedEquals([null, 'lib_a', 'lib_b', 'lib_c'])); - - code.forEach((chunk, code) { - var cu = parseCompilationUnit(code); - if (chunk.library == null) { - expectHasImports(cu, ['main.dart', 'common1.dart']); - } else if (chunk.library.name.endsWith('lib_a')) { - expectHasImports(cu, ['a.dart', 'a2.dart', 'common2.dart']); - } else if (chunk.library.name.endsWith('lib_b')) { - expectHasImports(cu, ['b.dart', 'b2.dart', 'common2.dart']); - } else if (chunk.library.name.endsWith('lib_c')) { - expectHasImports(cu, []); - } - }); - }); - }); -} - -expectHasImports(CompilationUnit cu, List expectedImports) { - var imports = []; - cu.directives.forEach((Directive directive) { - if (directive is NamespaceDirective) { - // 'Only expecting import, no exports.' - expect(directive).toBeAnInstanceOf(ImportDirective); - ImportDirective import = directive; - imports.add(import.uri.stringValue); - } - }); - expect(imports.length).toEqual(expectedImports.length); - for (int i = 0; i < imports.length; i++) { - expect(imports[i]).to(matcher.endsWith(expectedImports[i])); - } -} diff --git a/test/main.dart b/test/main.dart index a3600b0..81822b8 100644 --- a/test/main.dart +++ b/test/main.dart @@ -14,13 +14,18 @@ library di.tests; import 'package:guinness/guinness.dart'; import 'package:matcher/matcher.dart' as matcher; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; -import 'package:di/static_injector.dart'; import 'package:di/annotations.dart'; +import 'package:di/src/reflector_static.dart'; +import 'package:di/src/reflector_dynamic.dart'; +import 'package:di/check_bind_args.dart'; +import 'package:di/src/module.dart'; import 'test_annotations.dart'; // Generated file. Run ../test_tf_gen.sh. import 'type_factories_gen.dart' as type_factories_gen; +import 'main_same_name.dart' as same_name; + +import 'dart:mirrors'; /** * Annotation used to mark classes for which static type factory must be @@ -175,7 +180,7 @@ class AnnotatedPrimitiveDependency { } class EmulatedMockEngineFactory { - call(Injector i) => new MockEngine(); + call() => new MockEngine(); } bool throwOnceShouldThrow = true; @@ -189,22 +194,32 @@ class ThrowOnce { } } +@Injectable() +class SameEngine { + same_name.Engine engine; + SameEngine(this.engine); +} + + +const String STATIC_NAME = 'Static ModuleInjector'; +const String DYNAMIC_NAME = 'Dynamic ModuleInjector'; void main() { - moduleTest(); + testModule(); - createInjectorSpec('DynamicInjector', - (modules, [name]) => new DynamicInjector(modules: modules, name: name)); + var static_factory = new GeneratedTypeFactories( + type_factories_gen.typeFactories, type_factories_gen.parameterKeys); + createInjectorSpec(STATIC_NAME, + () => new Module.withReflector(static_factory)); - createInjectorSpec('StaticInjector', - (modules, [name]) => new StaticInjector(modules: modules, name: name, - typeFactories: type_factories_gen.typeFactories)); + TypeReflector reflector = new DynamicTypeFactories(); + createInjectorSpec(DYNAMIC_NAME, + () => new Module.withReflector(reflector)); - dynamicInjectorTest(); - staticInjectorTest(); - createKeySpec(); + testKey(); + testCheckBindArgs(); } -moduleTest() { +testModule() { describe('Module', () { @@ -221,35 +236,42 @@ moduleTest() { it('should throw if incorrect combination of parameters passed (2)', () { expect(() { - new Module().bind(Engine, toValue: new Engine(), toFactory: (_) => null); + new Module().bind(Engine, toValue: new Engine(), toFactory: () => null); }).toThrowWith(message: BIND_ERROR); }); it('should throw if incorrect combination of parameters passed (3)', () { expect(() { - new Module().bind(Engine, toValue: new Engine(), toImplementation: MockEngine, toFactory: (_) => null); + new Module().bind(Engine, toValue: new Engine(), toImplementation: MockEngine, toFactory: () => null); }).toThrowWith(message: BIND_ERROR); }); it('should throw if incorrect combination of parameters passed (4)', () { expect(() { - new Module().bind(Engine, toImplementation: MockEngine, toFactory: (_) => null); + new Module().bind(Engine, toImplementation: MockEngine, toFactory: () => null); }).toThrowWith(message: BIND_ERROR); }); + it('should throw when trying to bind primitive type', () { + expect(() { + new Module().bind(int, toValue: 3); + }).toThrowWith(message: "Cannot bind primitive type 'int'."); + }); + }); }); } -typedef Injector InjectorFactory(List modules, [String name]); +typedef Module ModuleFactory(); -createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { +createInjectorSpec(String injectorName, ModuleFactory moduleFactory) { describe(injectorName, () { it('should instantiate a type', () { - var injector = injectorFactory([new Module()..bind(Engine)]); + var module = moduleFactory()..bind(Engine); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance).toBeAnInstanceOf(Engine); @@ -257,7 +279,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should instantiate an annotated type', () { - var injector = injectorFactory([new Module() + var injector = new ModuleInjector([moduleFactory() ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) ..bind(Car, toValue: new Engine()) ]); @@ -267,17 +289,31 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(instance.id).toEqual('turbo-engine-id'); }); - it('should fail if no binding is found', () { - var injector = injectorFactory([]); + it('should fail if the type was not bound at injector creation', () { + var module = moduleFactory(); + var injector = new ModuleInjector([module]); + module.bind(Engine); + expect(() { injector.get(Engine); }).toThrowWith(message: 'No provider found for Engine! ' - '(resolving Engine)'); + '(resolving Engine)'); }); + it('should fail if no binding is found resolving dependencies', () { + var injector = new ModuleInjector([moduleFactory()..bind(Car)]); + expect(() { + injector.get(Car); + }).toThrowWith(message: 'No provider found for Engine! ' + '(resolving Car -> Engine)'); + }); it('should resolve basic dependencies', () { - var injector = injectorFactory([new Module()..bind(Car)..bind(Engine)]); + var injector = new ModuleInjector([ + moduleFactory() + ..bind(Car) + ..bind(Engine) + ]); var instance = injector.get(Car); expect(instance).toBeAnInstanceOf(Car); @@ -285,10 +321,10 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should resolve complex dependencies', () { - var injector = injectorFactory([new Module() - ..bind(Porsche) - ..bind(TurboEngine) - ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) + var injector = new ModuleInjector([moduleFactory() + ..bind(Porsche) + ..bind(TurboEngine) + ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) ]); var instance = injector.get(Porsche); @@ -297,9 +333,9 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should resolve annotated primitive type', () { - var injector = injectorFactory([new Module() - ..bind(AnnotatedPrimitiveDependency) - ..bind(String, toValue: 'Worked!', withAnnotation: Turbo) + var injector = new ModuleInjector([moduleFactory() + ..bind(AnnotatedPrimitiveDependency) + ..bind(String, toValue: 'Worked!', withAnnotation: Turbo) ]); var instance = injector.get(AnnotatedPrimitiveDependency); @@ -307,53 +343,59 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(instance.strValue).toEqual('Worked!'); }); + it('should instantiate parameterized types', () { + var module = moduleFactory()..bind(ParameterizedType); + var injector = new ModuleInjector([module]); + expect(injector.get(ParameterizedType)).toBeAnInstanceOf(ParameterizedType); + }); + it('should inject generic parameterized types', () { - var injector = injectorFactory([new Module() - ..bind(ParameterizedType) - ..bind(GenericParameterizedDependency) + var injector = new ModuleInjector([moduleFactory() + ..bind(ParameterizedType) + ..bind(GenericParameterizedDependency) ]); expect(injector.get(GenericParameterizedDependency)) .toBeAnInstanceOf(GenericParameterizedDependency); }); - - xit('should error while resolving parameterized types', () { - var injector = injectorFactory([new Module() - ..bind(ParameterizedType) - ..bind(ParameterizedDependency) - ]); - expect(() => injector.get(ParameterizedDependency)).toThrowWith(); + it('should error while resolving parameterized dependencies', () { + var module = moduleFactory(); + expect(() => module.bind(ParameterizedDependency)).toThrowWith( + message: + injectorName == STATIC_NAME ? + "Type 'ParameterizedDependency' not found in generated typeFactory maps. " + "Is the type's constructor injectable and annotated for injection?" : + "ParameterizedType cannot be injected because it is parameterized " + "with non-generic types." + ); }); - it('should allow modules and overriding providers', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); + var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); // injector is immutable // you can't load more modules once it's instantiated // (you can create a child injector) - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance.id).toEqual('mock-id'); }); - it('should only create a single instance', () { - var injector = injectorFactory([new Module()..bind(Engine)]); + var injector = new ModuleInjector([moduleFactory()..bind(Engine)]); var first = injector.get(Engine); var second = injector.get(Engine); expect(first).toBe(second); }); - it('should allow providing values', () { - var module = new Module() - ..bind(Engine, toValue: 'str value') - ..bind(Car, toValue: 123); + var module = moduleFactory() + ..bind(Engine, toValue: 'str value') + ..bind(Car, toValue: 123); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var abcInstance = injector.get(Engine); var complexInstance = injector.get(Car); @@ -361,64 +403,93 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(complexInstance).toEqual(123); }); - it('should allow providing null values', () { - var module = new Module() - ..bind(Engine, toValue: null); + var module = moduleFactory() + ..bind(Engine, toValue: null); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var engineInstance = injector.get(Engine); expect(engineInstance).toBeNull(); }); + it('should cache null values', () { + var count = 0; + factory() { + if (count++ == 0) return null; + return new Engine(); + } + var module = moduleFactory()..bind(Engine, toFactory: factory); + var injector = new ModuleInjector([module]); + + var engine = injector.get(Engine); + engine = injector.get(Engine); + + expect(engine).toBeNull(); + }); + + + it('should only call factories once, even when circular', () { + var count = 0; + factory(injector) { + count++; + return injector.get(Engine); + } + var module = moduleFactory()..bind(Engine, toFactory: factory, inject: [Injector]); + var injector = new ModuleInjector([module]); + + try { + var engine = injector.get(Engine); + } on CircularDependencyError catch (e) { + expect(count).toEqual(1); + } + }); + + it('should allow providing factory functions', () { - var module = new Module()..bind(Engine, toFactory: () { + var module = moduleFactory()..bind(Engine, toFactory: () { return 'factory-product'; }, inject: []); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance).toEqual('factory-product'); }); - it('should allow providing with emulated factory functions', () { - var module = new Module(); + var module = moduleFactory(); module.bind(Engine, toFactory: new EmulatedMockEngineFactory()); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance).toBeAnInstanceOf(MockEngine); }); - it('should inject injector into factory function', () { - var module = new Module() - ..bind(Engine) - ..bind(Car, toFactory: (Engine engine, Injector injector) { - return new Car(engine, injector); - }, inject: [Engine, Injector]); + var module = moduleFactory() + ..bind(Engine) + ..bind(Car, toFactory: (Engine engine, Injector injector) { + return new Car(engine, injector); + }, inject: [Engine, Injector]); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Car); expect(instance).toBeAnInstanceOf(Car); expect(instance.engine.id).toEqual('v8-id'); }); - it('should throw an exception when injecting a primitive type', () { - var injector = injectorFactory([ - new Module() - ..bind(NumDependency) - ..bind(IntDependency) - ..bind(DoubleDependency) - ..bind(BoolDependency) - ..bind(StringDependency) + var injector = new ModuleInjector([ + moduleFactory() + ..bind(NumDependency) + ..bind(IntDependency) + ..bind(DoubleDependency) + ..bind(BoolDependency) + ..bind(StringDependency) ]); expect(() { @@ -457,9 +528,8 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { '(resolving StringDependency -> String)'); }); - it('should throw an exception when circular dependency', () { - var injector = injectorFactory([new Module()..bind(CircularA) + var injector = new ModuleInjector([moduleFactory()..bind(CircularA) ..bind(CircularB)]); expect(() { @@ -471,8 +541,8 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should throw an exception when circular dependency in factory', () { - var injector = injectorFactory([new Module() - ..bind(CircularA, toFactory: (i) => i.get(CircularA)) + var injector = new ModuleInjector([moduleFactory() + ..bind(CircularA, inject: [CircularA]) ]); expect(() { @@ -483,9 +553,8 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { '(resolving CircularA -> CircularA)'); }); - it('should recover from errors', () { - var injector = injectorFactory([new Module()..bind(ThrowOnce)]); + var injector = new ModuleInjector([moduleFactory()..bind(ThrowOnce)]); throwOnceShouldThrow = true; var caught = false; @@ -498,46 +567,42 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(caught).toEqual(true); }); - it('should provide the injector as Injector', () { - var injector = injectorFactory([]); + var injector = new ModuleInjector([]); expect(injector.get(Injector)).toBe(injector); }); it('should inject a typedef', () { - var module = new Module()..bind(CompareInt, toValue: compareIntAsc); + var module = moduleFactory()..bind(CompareInt, toValue: compareIntAsc); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var compare = injector.get(CompareInt); expect(compare(1, 2)).toEqual(1); expect(compare(5, 2)).toEqual(-1); }); - it('should throw an exception when injecting typedef without providing it', () { - var injector = injectorFactory([new Module()..bind(WithTypeDefDependency)]); - expect(() { + var injector = new ModuleInjector([moduleFactory()..bind(WithTypeDefDependency)]); injector.get(WithTypeDefDependency); }).toThrowWith(); }); - it('should instantiate via the default/unnamed constructor', () { - var injector = injectorFactory([new Module()..bind(MultipleConstructors)]); + var injector = new ModuleInjector([moduleFactory()..bind(MultipleConstructors)]); MultipleConstructors instance = injector.get(MultipleConstructors); expect(instance.instantiatedVia).toEqual('default'); }); // CHILD INJECTORS it('should inject from child', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); + var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); - var parent = injectorFactory([new Module()..bind(Engine)]); - var child = parent.createChild([module]); + var parent = new ModuleInjector([moduleFactory()..bind(Engine)]); + var child = new ModuleInjector([module], parent); var abcFromParent = parent.get(Engine); var abcFromChild = child.get(Engine); @@ -546,21 +611,19 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(abcFromChild.id).toEqual('mock-id'); }); - it('should enumerate across children', () { - var parent = injectorFactory([new Module()..bind(Engine)]); - var child = parent.createChild([new Module()..bind(MockEngine)]); + var parent = new ModuleInjector([moduleFactory()..bind(Engine)]); + var child = new ModuleInjector([moduleFactory()..bind(MockEngine)], parent); expect(parent.types).to(matcher.unorderedEquals([Engine, Injector])); expect(child.types).to(matcher.unorderedEquals([Engine, MockEngine, Injector])); }); - it('should inject instance from parent if not provided in child', () { - var module = new Module()..bind(Car); + var module = moduleFactory()..bind(Car); - var parent = injectorFactory([new Module()..bind(Car)..bind(Engine)]); - var child = parent.createChild([module]); + var parent = new ModuleInjector([moduleFactory()..bind(Car)..bind(Engine)]); + var child = new ModuleInjector([module], parent); var complexFromParent = parent.get(Car); var complexFromChild = child.get(Car); @@ -571,12 +634,11 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(abcFromChild).toBe(abcFromParent); }); - it('should inject instance from parent but never use dependency from child', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); + var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); - var parent = injectorFactory([new Module()..bind(Car)..bind(Engine)]); - var child = parent.createChild([module]); + var parent = new ModuleInjector([moduleFactory()..bind(Car)..bind(Engine)]); + var child = new ModuleInjector([module], parent); var complexFromParent = parent.get(Car); var complexFromChild = child.get(Car); @@ -588,282 +650,30 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { expect(complexFromChild.engine).not.toBe(abcFromChild); }); - - it('should force new instance in child even if already instantiated in parent', () { - var parent = injectorFactory([new Module()..bind(Engine)]); - var abcAlreadyInParent = parent.get(Engine); - - var child = parent.createChild([], forceNewInstances: [new Key(Engine)]); - var abcFromChild = child.get(Engine); - - expect(abcFromChild).not.toBe(abcAlreadyInParent); - }); - - - it('should force new instance in child using provider from grand parent', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); - - var grandParent = injectorFactory([module]); - var parent = grandParent.createChild([]); - var child = parent.createChild([], forceNewInstances: [new Key(Engine)]); - - var abcFromGrandParent = grandParent.get(Engine); - var abcFromChild = child.get(Engine); - - expect(abcFromChild.id).toEqual('mock-id'); - expect(abcFromChild).not.toBe(abcFromGrandParent); - }); - - it('should provide child injector as Injector', () { - var injector = injectorFactory([]); - var child = injector.createChild([]); + var injector = new ModuleInjector([]); + var child = new ModuleInjector([], injector); expect(child.get(Injector)).toBe(child); }); - - it('should set the injector name', () { - var injector = injectorFactory([], 'foo'); - expect(injector.name).toEqual('foo'); - }); - - - it('should set the child injector name', () { - var injector = injectorFactory([], 'foo'); - var childInjector = injector.createChild(null, name: 'bar'); - expect(childInjector.name).toEqual('bar'); - }); - - it('should instantiate class only once (Issue #18)', () { - var rootInjector = injectorFactory([]); - var injector = rootInjector.createChild([ - new Module() - ..bind(Log) - ..bind(ClassOne) - ..bind(InterfaceOne, inject: [ClassOne]) - ]); + var rootInjector = new ModuleInjector([]); + var injector = new ModuleInjector([ + moduleFactory() + ..bind(Log) + ..bind(ClassOne) + ..bind(InterfaceOne, inject: [ClassOne]) + ], rootInjector); expect(injector.get(InterfaceOne)).toBe(injector.get(ClassOne)); expect(injector.get(Log).log.join(' ')).toEqual('ClassOne'); }); - - - describe('visiblity', () { - - it('should hide instances', () { - - var rootMock = new MockEngine(); - var childMock = new MockEngine(); - - var parentModule = new Module() - ..bind(Engine, toValue: rootMock); - var childModule = new Module() - ..bind(Engine, toValue: childMock, visibility: (_, __) => false); - - var parentInjector = injectorFactory([parentModule]); - var childInjector = parentInjector.createChild([childModule]); - - var val = childInjector.get(Engine); - expect(val).toBe(rootMock); - }); - - it('should throw when an instance in not visible in the root injector', () { - var module = new Module() - ..bind(Car, toValue: 'Invisible', visibility: (_, __) => false); - - var injector = injectorFactory([module]); - - expect(() { - injector.get(Car); - }).toThrowWith( - anInstanceOf: NoProviderError, - message: 'No provider found for Car! (resolving Car)' - ); - }); - }); - }); } -void dynamicInjectorTest() { - describe('DynamicInjector', () { - - it('should throw a comprehensible error message on untyped argument', () { - var module = new Module()..bind(Lemon)..bind(Engine); - var injector = new DynamicInjector(modules : [module]); - - expect(() { - injector.get(Lemon); - }).toThrowWith( - anInstanceOf: NoProviderError, - message: "The 'engine' parameter must be typed " - "(resolving Lemon)"); - }); - - it('should throw a comprehensible error message when no default constructor found', () { - var module = new Module()..bind(HiddenConstructor); - var injector = new DynamicInjector(modules: [module]); - - expect(() { - injector.get(HiddenConstructor); - }).toThrowWith( - anInstanceOf: NoProviderError, - message: 'Unable to find default constructor for HiddenConstructor.' - ' Make sure class has a default constructor.'); - }); - - it('should inject parameters into function and invoke it', () { - var module = new Module() - ..bind(Engine); - var id; - var injector = new DynamicInjector(modules: [module]); - injector.invoke((Engine e) => id = e.id); - expect(id).toEqual('v8-id'); - }); - - it('should inject annotated parameters into function and invoke it', () { - var module = new Module() - ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine); - var id; - var injector = new DynamicInjector(modules: [module]); - injector.invoke((@Turbo() Engine e) => id = e.id); - expect(id).toEqual('turbo-engine-id'); - }); - - it('should get an instance using implicit injection for an unseen type', (){ - var module = new Module() - ..bind(Engine); - var injector = - new DynamicInjector(modules: [module], allowImplicitInjection: true); - - expect(injector.get(SpecialEngine).id).toEqual('special-id'); - }); - - it('should give higher precedence to bindings in parent module', () { - var module = new Module() - ..bind(Engine); - var child = new Module() - ..bind(Engine, toImplementation:TurboEngine); - var injector = new DynamicInjector(modules: [child]); - module.install(child); - injector = new DynamicInjector(modules: [module]); - var id = injector.get(Engine).id; - expect(id).toEqual('v8-id'); - injector = new DynamicInjector(modules: [child]); - id = injector.get(Engine).id; - expect(id).toEqual('turbo-engine-id'); - }); - - }); -} - -void staticInjectorTest() { - describe('StaticInjector', () { - - it('should use type factories passed in the constructor', () { - var module = new Module() - ..bind(Engine); - var injector = new StaticInjector(modules: [module], typeFactories: { - Engine: (f) => new Engine() - }); - - var engine; - expect(() { - engine = injector.get(Engine); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Engine); - }); - it('should use type factories passes in one module', () { - var module = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - var injector = new StaticInjector(modules: [module]); - - var engine; - expect(() { - engine = injector.get(Engine); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Engine); - }); - - it('should use type factories passes in many modules', () { - var module1 = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - var module2 = new Module() - ..bind(Car) - ..typeFactories = { - Car: (f) => new Car(f(Engine), f(Injector)) - }; - - var injector = new StaticInjector(modules: [module1, module2]); - - var engine; - expect(() { - engine = injector.get(Car); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Car); - }); - - it('should use type factories passes in hierarchical module', () { - var module = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - - module.install(new Module() - ..bind(Car) - ..typeFactories = { - Car: (f) => new Car(f(Engine), f(Injector)) - }); - - var injector = new StaticInjector(modules: [module]); - - var engine; - expect(() { - engine = injector.get(Car); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Car); - }); - - it('should find type factories from parent injector', () { - var module1 = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - var module2 = new Module() - ..bind(Car) - ..typeFactories = { - Car: (f) => new Car(f(Engine), f(Injector)) - }; - - var rootInjector = new StaticInjector(modules: [module1]); - var childInjector = rootInjector.createChild([module2]); - - expect(() { - rootInjector.get(Car); - }).toThrowWith(); - - var engine; - expect(() { - engine = childInjector.get(Car); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Car); - }); - - }); -} - -createKeySpec() { +testKey() { describe('Key', () { void expectEquals(x, y, bool truthValue) { expect(x == y).toEqual(truthValue); @@ -896,5 +706,38 @@ createKeySpec() { ' is same', () { expectEquals(new Key(Engine, Old), new Key(Car, Old), false); }); + + it('should be equal to a mirrored key of the same type', () { + ClassMirror classMirror = reflectType(Car); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + + ParameterMirror p = ctor.parameters[0]; + var pType = (p.type as ClassMirror).reflectedType; + + expectEquals(new Key(Engine), new Key(pType), true); + }); + }); +} + + +testCheckBindArgs() { + describe('CheckBindArgs', () { + var _ = DEFAULT_VALUE; + it('should return true when args are well formed', () { + expect(checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine, Car])).toBeTrue(); + expect(checkBindArgs(_, () => 0, null, [])).toBeTrue(); + expect(checkBindArgs(0, _, null, [])).toBeTrue(); + expect(checkBindArgs(_, _, Car, [])).toBeTrue(); + }); + + it('should error when wrong number of args have been set', () { + expect(() => checkBindArgs(_, () => 0, Car, [])).toThrowWith(); + expect(() => checkBindArgs(0, _, null, [Engine, Car])).toThrowWith(); + }); + + it('should error when toFactory argument count does not match inject length', () { + expect(() => checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine])).toThrowWith(); + expect(() => checkBindArgs(_, () => 0, null, [Engine, Car])).toThrowWith(); + }); }); } diff --git a/test/main_same_name.dart b/test/main_same_name.dart new file mode 100644 index 0000000..8c42cd1 --- /dev/null +++ b/test/main_same_name.dart @@ -0,0 +1,6 @@ +library di.tests_same_name; + +import 'package:di/di.dart'; + +@Injectable() +class Engine {} diff --git a/test/injector_generator_spec.dart b/test/transformer_test.dart similarity index 75% rename from test/injector_generator_spec.dart rename to test/transformer_test.dart index 72972cf..dde949f 100644 --- a/test/injector_generator_spec.dart +++ b/test/transformer_test.dart @@ -5,8 +5,9 @@ import 'dart:async'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; import 'package:code_transformers/tests.dart' as tests; -import 'package:di/transformer/injector_generator.dart'; +import 'package:di/transformer.dart'; import 'package:di/transformer/options.dart'; +import 'package:di/transformer/injector_generator.dart'; import 'package:guinness/guinness.dart'; @@ -51,11 +52,19 @@ main() { "import 'package:a/engine.dart' as import_1;", "import 'package:a/seat.dart' as import_2;", ], - generators: [ - 'import_0.Car: (f) => new import_0.Car(f(import_1.Engine), ' - 'f(import_2.Seat)),', - 'import_1.Engine: (f) => new import_1.Engine(),', - 'import_2.Seat: (f) => new import_2.Seat(),', + keys: [ + 'Engine = new Key(import_1.Engine);', + 'Seat = new Key(import_2.Seat);', + ], + factories: [ + 'import_0.Car: (a1, a2) => new import_0.Car(a1, a2),', + 'import_1.Engine: () => new import_1.Engine(),', + 'import_2.Seat: () => new import_2.Seat(),', + ], + paramKeys: [ + 'import_0.Car: [_KEY_Engine, _KEY_Seat],', + 'import_1.Engine: const[],', + 'import_2.Seat: const[],' ]); }); @@ -74,8 +83,11 @@ main() { imports: [ "import 'package:a/a.dart' as import_0;", ], - generators: [ - 'import_0.Parameterized: (f) => new import_0.Parameterized(),', + factories: [ + 'import_0.Parameterized: () => new import_0.Parameterized(),', + ], + paramKeys: [ + 'import_0.Parameterized: const[],', ], messages: [ 'warning: Parameterized is a parameterized type. ' @@ -118,8 +130,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Bar: (f) => new import_0.Bar(f(import_0.Foo)),', + keys: [ + "Foo = new Key(import_0.Foo);" + ], + factories: [ + 'import_0.Bar: (a1) => new import_0.Bar(a1),', + ], + paramKeys: [ + 'import_0.Bar: [_KEY_Foo],' ]); }); @@ -133,8 +151,11 @@ main() { imports: [ "import 'package:a/b.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -156,8 +177,11 @@ main() { imports: [ "import 'package:a/a.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -171,8 +195,11 @@ main() { imports: [ "import 'package:a/b.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -194,9 +221,16 @@ main() { "import 'package:a/a.dart' as import_0;", "import 'package:a/b.dart' as import_1;", ], - generators: [ - 'import_0.Car: (f) => new import_0.Car(f(import_1.Engine)),', - 'import_1.Engine: (f) => new import_1.Engine(),', + keys: [ + "Engine = new Key(import_1.Engine);" + ], + factories: [ + 'import_0.Car: (a1) => new import_0.Car(a1),', + 'import_1.Engine: () => new import_1.Engine(),', + ], + paramKeys: [ + 'import_0.Car: [_KEY_Engine],', + 'import_1.Engine: const[],', ]); }); @@ -209,8 +243,11 @@ main() { imports: [ "import 'a.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -225,8 +262,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -261,8 +301,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -333,8 +376,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -357,8 +403,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(f(import_0.Fuel)),', + keys: [ + "Fuel = new Key(import_0.Fuel);", + ], + factories: [ + 'import_0.Engine: (a1) => new import_0.Engine(a1),', + ], + paramKeys: [ + 'import_0.Engine: [_KEY_Fuel],' ]); }); @@ -382,9 +434,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => ' - 'new import_0.Engine(f(import_0.JetFuel)),', + keys: [ + "JetFuel = new Key(import_0.JetFuel);", + ], + factories: [ + 'import_0.Engine: (a1) => new import_0.Engine(a1),', + ], + paramKeys: [ + 'import_0.Engine: [_KEY_JetFuel],' ]); }); @@ -436,9 +493,13 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', - 'import_0.Car: (f) => new import_0.Car(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + 'import_0.Car: () => new import_0.Car(),', + ], + paramKeys: [ + 'import_0.Engine: const[],', + 'import_0.Car: const[],' ]).whenComplete(() { injectableAnnotations.clear(); }); @@ -449,16 +510,16 @@ main() { inputs: { 'a|web/main.dart': ''' import "package:inject/inject.dart"; - class Engine { - final Car car; + class Car { + final Engine engine; @inject - Engine([Car this.car]); + Car([Engine this.engine]); } - class Car { + class Engine { @inject - Car(); + Engine(); } main() {} @@ -467,9 +528,16 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(f(import_0.Car)),', - 'import_0.Car: (f) => new import_0.Car(),', + keys: [ + "Engine = new Key(import_0.Engine);" + ], + factories: [ + 'import_0.Car: (a1) => new import_0.Car(a1),', + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Car: [_KEY_Engine],', + 'import_0.Engine: const[],', ]); }); @@ -493,8 +561,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -514,8 +585,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(f(int)),', + keys: [ + "int = new Key(int);" + ], + factories: [ + 'import_0.Engine: (a1) => new import_0.Engine(a1),', + ], + paramKeys: [ + 'import_0.Engine: [_KEY_int],' ]); }); @@ -557,41 +634,6 @@ main() { 'annotated for injection. (web/main.dart 2 18)']); }); - it('transforms main', () { - return tests.applyTransformers(phases, - inputs: { - 'a|web/main.dart': ''' -library main; -import 'package:di/auto_injector.dart'; -import 'package:di/auto_injector.dart' as ai; - -main() { - var module = defaultInjector(modules: null, name: 'foo'); - print(module); - - var module2 = ai.defaultInjector(modules: null, name: 'foo'); - print(module2); -}''', - 'di|lib/auto_injector.dart': PACKAGE_AUTO - }, - results: { - 'a|web/main.dart': ''' -library main; -import 'main_static_injector.dart' as generated_static_injector; -import 'package:di/auto_injector.dart'; -import 'package:di/auto_injector.dart' as ai; - -main() { - var module = generated_static_injector.createStaticInjector(modules: null, name: 'foo'); - print(module); - - var module2 = generated_static_injector.createStaticInjector(modules: null, name: 'foo'); - print(module2); -}''' - - }); - }); - it('handles annotated dependencies', () { return generates(phases, inputs: { @@ -616,55 +658,82 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', - 'import_0.Car: (f) => new import_0.Car(f(import_0.Engine, import_0.Turbo)),', + keys: [ + "Engine_Turbo = new Key(import_0.Engine, import_0.Turbo);" + ], + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + 'import_0.Car: (a1) => new import_0.Car(a1),', + ], + paramKeys: [ + 'import_0.Engine: const[],', + 'import_0.Car: [_KEY_Engine_Turbo],' ]); }); + + it('transforms main', () { + return tests.applyTransformers(phases, + inputs: { + 'a|web/main.dart': ''' +library main; +import 'package:di/di.dart'; + +main() { + print('abc'); +}''' + }, + results: { + 'a|web/main.dart': ''' +library main; +import 'package:di/di.dart'; +import 'main_generated_type_factory_maps.dart' show setStaticReflectorAsDefault; + +main() { + setStaticReflectorAsDefault(); + print('abc'); +}''' + }); + }); }); } Future generates(List> phases, {Map inputs, Iterable imports: const [], - Iterable generators: const [], + Iterable keys: const [], + Iterable factories: const [], + Iterable paramKeys: const [], Iterable messages: const []}) { inputs['inject|lib/inject.dart'] = PACKAGE_INJECT; imports = imports.map((i) => '$i\n'); - generators = generators.map((t) => ' $t\n'); + keys = keys.map((t) => 'final Key _KEY_$t'); + factories = factories.map((t) => ' $t\n'); + paramKeys = paramKeys.map((t) => ' $t\n'); return tests.applyTransformers(phases, inputs: inputs, results: { - 'a|web/main_static_injector.dart': ''' + 'a|web/main_generated_type_factory_maps.dart': ''' $IMPORTS -${imports.join('')}$BOILER_PLATE -${generators.join('')}$FOOTER +${imports.join('')}${(keys.length != 0 ? '\n' : '')}${keys.join('\n')} +final Map typeFactories = { +${factories.join('')}}; +final Map> parameterKeys = { +${paramKeys.join('')}}; +setStaticReflectorAsDefault() => Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys); ''', }, messages: messages); } const String IMPORTS = ''' -library a.web.main.generated_static_injector; +library a.web.main.generated_type_factory_maps; import 'package:di/di.dart'; -import 'package:di/static_injector.dart'; +import 'package:di/src/reflector_static.dart'; '''; -const String BOILER_PLATE = ''' -Injector createStaticInjector({List modules, String name, - bool allowImplicitInjection: false}) => - new StaticInjector(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection, - typeFactories: factories); - -final Map factories = {'''; - -const String FOOTER = ''' -};'''; - const String CLASS_ENGINE = ''' import 'package:inject/inject.dart'; class Engine { @@ -697,10 +766,3 @@ class Injectables { const Injectables(this.types); } '''; - -const String PACKAGE_AUTO = ''' -library di.auto_injector; - -defaultInjector({List modules, String name, - bool allowImplicitInjection: false}) => null; -'''; diff --git a/test_tf_gen.sh b/test_tf_gen.sh index 3241bca..13b2563 100755 --- a/test_tf_gen.sh +++ b/test_tf_gen.sh @@ -8,5 +8,5 @@ fi set -v -dart bin/generator.dart $DART_SDK test/main.dart di.tests.InjectableTest test/type_factories_gen.dart packages/ +dart --checked bin/generator.dart $DART_SDK test/main.dart di.tests.InjectableTest test/type_factories_gen.dart packages/ From f878cfe3b87157245bfe651dd75b8c2b363179de Mon Sep 17 00:00:00 2001 From: Anting Shen Date: Mon, 21 Jul 2014 13:05:15 -0700 Subject: [PATCH 4/5] feat(module): toInstanceOf bind syntax --- lib/check_bind_args.dart | 5 +++-- lib/src/module.dart | 34 +++++++++++++++++++++------------- pubspec.yaml | 2 +- test/main.dart | 24 +++++++++++++----------- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/lib/check_bind_args.dart b/lib/check_bind_args.dart index 48e6c55..5db8d93 100644 --- a/lib/check_bind_args.dart +++ b/lib/check_bind_args.dart @@ -4,7 +4,7 @@ import "src/module.dart"; export "src/module.dart" show DEFAULT_VALUE, IDENTITY, isSet, isNotSet; checkBindArgs(dynamic toValue, Function toFactory, - Type toImplementation, List inject) { + Type toImplementation, List inject, Type toInstanceOf) { int count = 0; bool argCountMatch = true; if (isSet(toValue)) count++; @@ -33,9 +33,10 @@ checkBindArgs(dynamic toValue, Function toFactory, } if (toImplementation != null) count++; + if (toInstanceOf != null) count++; if (count > 1) { throw 'Only one of following parameters can be specified: ' - 'toValue, toFactory, toImplementation'; + 'toValue, toFactory, toImplementation, toInstanceOf'; } if (inject.isNotEmpty && isNotSet(toFactory)) { diff --git a/lib/src/module.dart b/lib/src/module.dart index 4f961f4..6c8f510 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -13,6 +13,7 @@ class Binding { Key key; List parameterKeys; Function factory; + static bool printInjectWarning = true; _checkPrimitive(Key key) { if (PRIMITIVE_TYPES.contains(key)) { @@ -23,14 +24,23 @@ class Binding { void bind(k, TypeReflector reflector, {toValue: DEFAULT_VALUE, Function toFactory: DEFAULT_VALUE, Type toImplementation, - List inject: const[]}) { + List inject: const[], Type toInstanceOf}) { key = k; assert(_checkPrimitive(k)); if (inject.length == 1 && isNotSet(toFactory)) { + if (printInjectWarning) { + print("${k.type}: Inject list without toFactory is deprecated. " + "Use `toInstanceOf: Type` instead."); + printInjectWarning = false; + } toFactory = IDENTITY; } - assert(checkBindArgs(toValue, toFactory, toImplementation, inject)); + assert(checkBindArgs(toValue, toFactory, toImplementation, inject, toInstanceOf)); + if (toInstanceOf != null) { + toFactory = IDENTITY; + inject = [toInstanceOf]; + } if (isSet(toValue)) { factory = () => toValue; parameterKeys = const []; @@ -93,22 +103,20 @@ class Module { * * * [toImplementation]: The given type will be instantiated using the [new] * operator and the resulting instance will be injected. - * * [toFactory]: The result of the factory function called with the types of [inject] as arguments - * is the value that will be injected. + * * [toFactory]: The result of the factory function called with the types of [inject] as + * arguments is the value that will be injected. * * [toValue]: The given value will be injected. + * * [toInstanceOf]: An instance of the given type will be fetched with DI. This is shorthand for + * toFactory: (x) => x, inject: [X]. * * [withAnnotation]: Type decorated with additional annotation. * * Up to one (0 or 1) of the following parameters can be specified at the - * same time: [toImplementation], [toFactory], [toValue]. - * - * If [inject] is a single item list with no other parameters specified, the specified - * type will be instantiated using its current binding. This is shorthand for - * toFactory: (item) => item, inject: [item]. + * same time: [toImplementation], [toFactory], [toValue], [toInstanceOf]. */ void bind(Type type, {dynamic toValue: DEFAULT_VALUE, Function toFactory: DEFAULT_VALUE, Type toImplementation, - List inject: const [], Type withAnnotation}) { - bindByKey(new Key(type, withAnnotation), toValue: toValue, + List inject: const [], Type toInstanceOf, Type withAnnotation}) { + bindByKey(new Key(type, withAnnotation), toValue: toValue, toInstanceOf: toInstanceOf, toFactory: toFactory, toImplementation: toImplementation, inject: inject); } @@ -116,11 +124,11 @@ class Module { * Same as [bind] except it takes [Key] instead of * [Type] [withAnnotation] combination. Faster. */ - void bindByKey(Key key, {dynamic toValue: DEFAULT_VALUE, + void bindByKey(Key key, {dynamic toValue: DEFAULT_VALUE, Type toInstanceOf, Function toFactory: DEFAULT_VALUE, List inject: const [], Type toImplementation}) { var binding = new Binding(); - binding.bind(key, reflector, toValue: toValue, toFactory: toFactory, + binding.bind(key, reflector, toValue: toValue, toFactory: toFactory, toInstanceOf: toInstanceOf, toImplementation: toImplementation, inject: inject); bindings[key] = binding; } diff --git a/pubspec.yaml b/pubspec.yaml index 6490278..dc9fa62 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: di -version: 2.0.0-alpha.11 +version: 2.0.0-alpha.12 authors: - Vojta Jina - Pavel Jbanov diff --git a/test/main.dart b/test/main.dart index 81822b8..f3d1886 100644 --- a/test/main.dart +++ b/test/main.dart @@ -224,7 +224,7 @@ testModule() { describe('Module', () { const BIND_ERROR = 'Only one of following parameters can be specified: ' - 'toValue, toFactory, toImplementation'; + 'toValue, toFactory, toImplementation, toInstanceOf'; describe('bind', () { @@ -542,7 +542,7 @@ createInjectorSpec(String injectorName, ModuleFactory moduleFactory) { it('should throw an exception when circular dependency in factory', () { var injector = new ModuleInjector([moduleFactory() - ..bind(CircularA, inject: [CircularA]) + ..bind(CircularA, toInstanceOf: CircularA) ]); expect(() { @@ -663,7 +663,7 @@ createInjectorSpec(String injectorName, ModuleFactory moduleFactory) { moduleFactory() ..bind(Log) ..bind(ClassOne) - ..bind(InterfaceOne, inject: [ClassOne]) + ..bind(InterfaceOne, toInstanceOf: ClassOne) ], rootInjector); expect(injector.get(InterfaceOne)).toBe(injector.get(ClassOne)); @@ -724,20 +724,22 @@ testCheckBindArgs() { describe('CheckBindArgs', () { var _ = DEFAULT_VALUE; it('should return true when args are well formed', () { - expect(checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine, Car])).toBeTrue(); - expect(checkBindArgs(_, () => 0, null, [])).toBeTrue(); - expect(checkBindArgs(0, _, null, [])).toBeTrue(); - expect(checkBindArgs(_, _, Car, [])).toBeTrue(); + expect(checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine, Car], null)).toBeTrue(); + expect(checkBindArgs(_, () => 0, null, [], null)).toBeTrue(); + expect(checkBindArgs(0, _, null, [], null)).toBeTrue(); + expect(checkBindArgs(_, _, Car, [], null)).toBeTrue(); + expect(checkBindArgs(_, _, null, [], Car)).toBeTrue(); }); it('should error when wrong number of args have been set', () { - expect(() => checkBindArgs(_, () => 0, Car, [])).toThrowWith(); - expect(() => checkBindArgs(0, _, null, [Engine, Car])).toThrowWith(); + expect(() => checkBindArgs(_, () => 0, Car, [], null)).toThrowWith(); + expect(() => checkBindArgs(0, _, null, [Engine, Car], null)).toThrowWith(); + expect(() => checkBindArgs(_, () => 0, null, [], Car)).toThrowWith(); }); it('should error when toFactory argument count does not match inject length', () { - expect(() => checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine])).toThrowWith(); - expect(() => checkBindArgs(_, () => 0, null, [Engine, Car])).toThrowWith(); + expect(() => checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine], null)).toThrowWith(); + expect(() => checkBindArgs(_, () => 0, null, [Engine, Car], null)).toThrowWith(); }); }); } From 279be49a8d62166ee7a8f794b8ec5b6059a0449a Mon Sep 17 00:00:00 2001 From: Anting Shen Date: Wed, 23 Jul 2014 11:31:33 -0700 Subject: [PATCH 5/5] chore(pubspec): Publish 2.0.0 --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd4592a..78d714e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,60 @@ +# 2.0.0 + +## Breaking Changes + +### Calls to `StaticInjector` and `DynamicInjector` should be replaced with `ModuleInjector` + - There are no longer `StaticInjectors` and `DynamicInjectors`. They have been replaced + by a new `ModuleInjector` class that acts as both types of injectors. + +### ModuleInjectors have no visibility + - All bindings and instances of parent injectors are now visible in child injectors. + - The optional argument `forceNewInstances` of `Injector.createChild` has been removed + Instead, create a new module with bindings of the types that require new instances + and pass that to the child injector, and the child injector will create new + instances instead of returning the instance of the parent injector. + +### Use `new ModuleInjector(modules, parent)` instead of `Injector.createChild(modules)` + - The latter is still available but deprecated. + - Injectors with no parent now have a dummy RootInjector instance as the parent + Instead of checking “parent == null”, check for “parent == rootInjector”. + +### Injectors no longer have a name field + +### typeFactories have changed + - Old type factories had the form `(injector) => new Instance(injector.get(dep1), … )` + - New factories have the form: + - `toFactory(a0, a1, …) => new Instance(a0, a1, …)` + - When calling `Module.bind(toFactory: factory)`, there is an additional argument `inject` + of a list of types or keys (preferred for performance) whose instances should be + passed to the factory. The arguments passed to the factory function will be instances + of the types in `inject`. + + Example: + - Old code `module.bind(Car, toFactory: (i) => new Car(i.get(Engine)));` + - New code + - `module.bind(Car, toFactory: (engine) => new Car(engine), inject: [Engine]);` + + There is also some syntactic sugar for this special case. + - Old code `module.bind(V8Engine, toFactory: (i) => i.get(Engine));` + - New code `module.bind(V8Engine, toFactory: (e) => e, inject: [Engine]);` + - With sugar `module.bind(V8Engine, toInstanceOf: Engine);` + +### Modules have a `TypeReflector` instance attached + - The `TypeReflector` is how the module will find the `toFactory` and `inject` + arguments when not explicitly specified. This is either done with mirroring or code + generation via transformers. Transformers will set the default to use code gen. + For testing and other special purposes where a specific reflector is needed, use + `new Module.withReflector(reflector)`. + +### The transformer has been updated + - Running the transformer will do the necessary code generation and edits to switch the + default `TypeReflector` from mirroring to static factories. Enable transformer to use + static factories, disable to use mirrors. More docs on the transformer can be found in + `transformer.dart` + +### Deprecated module methods removed + - `.value`, `.type`, `.factory`, `.factoryByKey` are gone. Use `..bind`. + # 1.2.3 ## Features diff --git a/pubspec.yaml b/pubspec.yaml index dc9fa62..f11c037 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: di -version: 2.0.0-alpha.12 +version: 2.0.0 authors: - Vojta Jina - Pavel Jbanov