From d75f18c8d5b24b44d7fc5a7cd3ef318e3c8adb0e Mon Sep 17 00:00:00 2001 From: Todd Wieck Date: Thu, 13 Aug 2015 18:02:40 -0700 Subject: [PATCH] fix(transformer): support injecting a generic type with a non-primitive parameter Updates the transformer to allow injecting generic types with non-primitive parameters. E.g. allows injecting types like Foo or ClassA>. --- lib/transformer/injector_generator.dart | 80 ++++++++++++++++++++----- test/transformer_test.dart | 28 +++++++++ 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/lib/transformer/injector_generator.dart b/lib/transformer/injector_generator.dart index 9d00d0b..c92ce40 100644 --- a/lib/transformer/injector_generator.dart +++ b/lib/transformer/injector_generator.dart @@ -277,22 +277,70 @@ class _Processor { var ctorTypes = constructors.map((ctor) => ctor.enclosingElement).toSet(); var usedLibs = new Set(); - String resolveClassName(ClassElement type, [List typeArgs]) { - var library = type.library; - usedLibs.add(library); - - var prefix = prefixes[library]; - if (prefix == null) { - prefix = prefixes[library] = - library.isDartCore ? '' : 'import_${prefixes.length}'; + + // Returns a Dart-type expression representing [instanceType] and which is + // suitable for use as an r-value. E.g.: Foo can be used as a generic + // type parameter, but not as an r-value, since t = Foo is a syntax + // error. + // + // When [instanceType] has no generic paramaters, this method returns code + // in the format 'import_1.Foo', but when [instanceType] has generic + // parameters, this method returns code that instantiates a [TypeLiteral], + // e.g. `new TypeLiteral>().type`. + // + // Assumes that [instanceTypeGenericTypes] is the types of the generic + // parameters of [instanceType], if any. + String resolveClassIdentifier(ParameterizedType instanceType, + [List instanceTypeGenericTypes = const [], + bool recursiveCall = false]) { + + // Returns import_xxx.{instanceType}. + String resolveRawType() { + // Short circuit if it's a dart core type. We don't want to resolve. + if (instanceType.element.library.isDartCore) { + return instanceType.name; + } + var library = instanceType.element.library; + usedLibs.add(library); + + var prefix = prefixes[library]; + if (prefix == null) { + prefix = prefixes[library] = + library.isDartCore ? '' : 'import_${prefixes.length}'; + } + if (prefix.isNotEmpty) { + prefix = '$prefix.'; + } + return '$prefix${instanceType.name}'; } - if (prefix.isNotEmpty) { - prefix = '$prefix.'; + + // 1. If we have generics, we must use a type literal. + // To prevent invalid syntax, new TypeLiteral object is only instantiated + // if this is the first (e.g. non-recursive) call into + // resolveClassIdentifier. + if (!recursiveCall && instanceTypeGenericTypes.any((type) => type.displayName != 'dynamic')) { + return 'new TypeLiteral<${resolveClassIdentifier(instanceType, instanceTypeGenericTypes, true)}>().type'; } - if (typeArgs == null || typeArgs.isEmpty || !typeArgs.any((arg) => arg is! DynamicTypeImpl)) { - return '$prefix${type.name}'; + + // 2. If we do not have any typed generics, immediately resolve the type. + // Returns import_xxx.{instanceType}. + if (instanceTypeGenericTypes + .every((type) => type.displayName == 'dynamic')) { + return resolveRawType(); } - return 'new TypeLiteral<$prefix${type.name}<${typeArgs.join(', ')}>>().type'; + + // 3. If we DO have generics, continue recursing. Returns + // import_xxx.{instanceType}. + final resolvedGenericTypes = instanceTypeGenericTypes.map((genericType) { + if (genericType is DynamicTypeImpl) { + return genericType.displayName; + } + return resolveClassIdentifier(genericType, + genericType is ParameterizedType + ? genericType.typeArguments + : const [], true); + }); + return '${resolveRawType()}<${resolvedGenericTypes.join(', ')}>'; } var keysBuffer = new StringBuffer(); @@ -301,7 +349,7 @@ class _Processor { var addedKeys = new Set(); for (var ctor in constructors) { ClassElement type = ctor.enclosingElement; - String typeName = resolveClassName(type); + String typeName = resolveClassIdentifier(type.type); String args = new List.generate(ctor.parameters.length, (i) => 'a${i+1}').join(', '); factoriesBuffer.write(' $typeName: ($args) => new $typeName($args),\n'); @@ -309,7 +357,7 @@ class _Processor { paramsBuffer.write(' $typeName: '); paramsBuffer.write(ctor.parameters.isEmpty ? 'const[' : '['); var params = ctor.parameters.map((param) { - String typeName = resolveClassName(param.type.element, (param.type).typeArguments); + String typeName = resolveClassIdentifier(param.type, param.type.typeArguments); Iterable annotations = []; if (param.metadata.isNotEmpty) { annotations = param.metadata.map( @@ -323,7 +371,7 @@ class _Processor { } if (addedKeys.add(keyName)) { keysBuffer.writeln('final Key $keyName = new Key($typeName' + - (annotations.isNotEmpty ? ', ${resolveClassName(annotations.first)});' : ');')); + (annotations.isNotEmpty ? ', ${resolveClassIdentifier(annotations.first.type)});' : ');')); } return keyName; }); diff --git a/test/transformer_test.dart b/test/transformer_test.dart index 86609bb..9fd136c 100644 --- a/test/transformer_test.dart +++ b/test/transformer_test.dart @@ -128,6 +128,34 @@ main() { ]); }); + it('injects nested parameterized constructor parameters', () { + return generates(phases, + inputs: { + 'a|web/main.dart': 'import "package:a/a.dart"; main() {}', + 'a|lib/a.dart': ''' + import 'package:inject/inject.dart'; + class Foo {} + class Spam {} + class Bar { + @inject + Bar(Foo> f); + } + ''' + }, + imports: [ + "import 'package:a/a.dart' as import_0;", + ], + keys: [ + 'Foo_Spam = new Key(new TypeLiteral>>().type);', + ], + factories: [ + 'import_0.Bar: (a1) => new import_0.Bar(a1),', + ], + paramKeys: [ + 'import_0.Bar: [_KEY_Foo_Spam],', + ]); + }); + it('allows un-parameterized parameters', () { return generates(phases, inputs: {