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..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 @@ -35,7 +92,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..5db8d93 --- /dev/null +++ b/lib/check_bind_args.dart @@ -0,0 +1,66 @@ +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, Type toInstanceOf) { + 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 (toInstanceOf != null) count++; + if (count > 1) { + throw 'Only one of following parameters can be specified: ' + 'toValue, toFactory, toImplementation, toInstanceOf'; + } + + 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..6c8f510 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -1,26 +1,66 @@ -part of di; - -// 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; - -_DEFAULT_VALUE(_) => null; +library di.module; + +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; +IDENTITY(p) => p; + +class Binding { + Key key; + List parameterKeys; + Function factory; + static bool printInjectWarning = true; + + _checkPrimitive(Key key) { + if (PRIMITIVE_TYPES.contains(key)) { + throw "Cannot bind primitive type '${key.type}'."; + } + return true; + } -typedef dynamic FactoryFn(Injector injector); + void bind(k, TypeReflector reflector, {toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, Type toImplementation, + 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, toInstanceOf)); -/** - * 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); + if (toInstanceOf != null) { + toFactory = IDENTITY; + inject = [toInstanceOf]; + } + 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); + } + } +} -/** - * Produces an instance of some type, provided [factory] produces instances of - * the dependencies that type. - */ -typedef Object TypeFactory(factory(Type type, Type annotation)); +bool isSet(val) => !identical(val, DEFAULT_VALUE); +bool isNotSet(val) => !isSet(val); /** * Module contributes configuration information to an [Injector] by providing @@ -31,32 +71,28 @@ typedef Object TypeFactory(factory(Type type, Type annotation)); * no effect on that injector. */ class Module { - final _providers = {}; - final _childModules = []; - Map _typeFactories = {}; + static TypeReflector DEFAULT_REFLECTOR = getReflector(); - Map get typeFactories { - if (_childModules.isEmpty) return _typeFactories; + /** + * 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; - var factories = new Map.from(_typeFactories); - _childModules.forEach((m) { - if (m.typeFactories != null) { - factories.addAll(m.typeFactories); - } - }); - return factories; - } + Module(): reflector = DEFAULT_REFLECTOR; - set typeFactories(Map factories) { - _typeFactories = factories; - } + /** + * Use a custom reflector instead of the default. Useful for testing purposes. + */ + Module.withReflector(this.reflector); - void updateListWithBindings(List providers) { - _childModules.forEach((child) => child.updateListWithBindings(providers)); - _providers.forEach((k, v) { - providers[k] = v; - }); - } + 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 +103,33 @@ 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. + * * [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. - * * [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]. + * same time: [toImplementation], [toFactory], [toValue], [toInstanceOf]. */ - void bind(Type type, {dynamic toValue: _DEFAULT_VALUE, - Function toFactory: _DEFAULT_VALUE, Type toImplementation, - Type withAnnotation, Visibility visibility, List inject}) { - bindByKey(new Key(type, withAnnotation), toValue: toValue, - toFactory: toFactory, toImplementation: toImplementation, - visibility: visibility, inject: inject); + void bind(Type type, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, Type toImplementation, + List inject: const [], Type toInstanceOf, Type withAnnotation}) { + bindByKey(new Key(type, withAnnotation), toValue: toValue, toInstanceOf: toInstanceOf, + 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, Type toInstanceOf, + 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, toInstanceOf: toInstanceOf, + 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 e7bb1ed..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 @@ -77,10 +97,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, @@ -123,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..f11c037 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,22 @@ name: di -version: 1.2.3 +version: 2.0.0 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 3341ae8..0e3744d 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,33 +1,55 @@ - #!/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 -echo "run tests in node" -node out/main.dart.js - -echo "run transformer tests" -pub build --mode=debug test - -echo "running transformer test (uncompiled, Dynamic DI)" -dart --checked test/auto_injector_test.dart +# 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 transformer test (Static DI, Dart VM)" -dart --checked build/test/auto_injector_test.dart +echo "Running compiled tests in node..." +node out/main.js -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..f3d1886 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,27 +194,37 @@ 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', () { const BIND_ERROR = 'Only one of following parameters can be specified: ' - 'toValue, toFactory, toImplementation'; + 'toValue, toFactory, toImplementation, toInstanceOf'; describe('bind', () { @@ -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, toInstanceOf: 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, toInstanceOf: 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,40 @@ 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], 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, [], 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], null)).toThrowWith(); + expect(() => checkBindArgs(_, () => 0, null, [Engine, Car], null)).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_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); 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/