Skip to content

Commit e3e9530

Browse files
committed
Support type qualifiers in type arguments
Fixes #409
1 parent c385b80 commit e3e9530

File tree

7 files changed

+99
-62
lines changed

7 files changed

+99
-62
lines changed

integration-tests/common-test/src/test/kotlin/me/tatarka/inject/test/MultibindsTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package me.tatarka.inject.test
22

33
import assertk.assertThat
4+
import assertk.assertions.containsExactlyInAnyOrder
45
import assertk.assertions.containsOnly
56
import kotlin.test.Test
67

@@ -70,4 +71,14 @@ class MultibindsTest {
7071
assertThat(component.stringMap).containsOnly("1" to "string")
7172
assertThat(component.myStringMap).containsOnly("1" to "myString")
7273
}
74+
75+
@Test
76+
fun generate_a_component_with_qualified_multibindings() {
77+
val component: MultibindWithQualifiersComponent = MultibindWithQualifiersComponent::class.create()
78+
79+
assertThat(component.set1).containsExactlyInAnyOrder("1")
80+
assertThat(component.set2).containsExactlyInAnyOrder("2")
81+
assertThat(component.map1.toList()).containsExactlyInAnyOrder("1" to "2")
82+
assertThat(component.map2.toList()).containsExactlyInAnyOrder("2" to "1")
83+
}
7384
}

integration-tests/common/src/main/kotlin/me/tatarka/inject/test/Multibinds.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ abstract class SetComponent {
2424
@Provides @IntoSet get() = FooValue("2")
2525
}
2626

27+
@Component
28+
abstract class MultibindWithQualifiersComponent {
29+
abstract val set1: Set<@Named("foo") String>
30+
abstract val set2: Set<@Named("bar") String>
31+
abstract val map1: Map<@Named("bar") String, @Named("foo") String>
32+
abstract val map2: Map<@Named("foo") String, @Named("bar") String>
33+
34+
@Provides
35+
@IntoSet
36+
protected fun fooSet(): @Named("foo") String = "1"
37+
38+
@Provides
39+
@IntoSet
40+
protected fun barSet(): @Named("bar") String = "2"
41+
42+
@Provides
43+
@IntoMap
44+
protected fun fooMap(): Pair<@Named("foo") String, @Named("bar") String> = "2" to "1"
45+
46+
@Provides
47+
@IntoMap
48+
protected fun barMap(): Pair<@Named("bar") String, @Named("foo") String> = "1" to "2"
49+
}
50+
2751
@Component
2852
abstract class DynamicKeyComponent {
2953

kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import me.tatarka.kotlin.ast.AstProvider
2121
import me.tatarka.kotlin.ast.AstType
2222
import me.tatarka.kotlin.ast.AstVisibility
2323
import me.tatarka.kotlin.ast.Messenger
24-
import me.tatarka.kotlin.ast.annotationAnnotatedWith
2524
import me.tatarka.kotlin.ast.hasAnnotation
2625

2726
private const val ANNOTATION_PACKAGE_NAME = "me.tatarka.inject.annotations"
@@ -342,14 +341,17 @@ fun findAssistedFactoryInjectFunction(
342341
return function
343342
}
344343

344+
fun AstType.typeQualifierAnnotations() =
345+
annotationsAnnotatedWith(QUALIFIER.packageName, QUALIFIER.simpleName)
346+
345347
fun <E> qualifier(
346348
provider: AstProvider,
347349
options: Options,
348350
element: E?,
349351
type: AstType,
350352
): AstAnnotation? where E : AstElement, E : AstAnnotated {
351353
// check for qualifiers incorrectly applied to type arguments
352-
fun checkTypeArgs(packageName: String, simpleName: String, type: AstType) {
354+
fun checkTypeArgs(type: AstType) {
353355
@Suppress("SwallowedException")
354356
val arguments = try {
355357
type.arguments
@@ -360,52 +362,28 @@ fun <E> qualifier(
360362
}
361363

362364
for (typeArg in arguments) {
363-
val argQualifier = typeArg.annotationAnnotatedWith(packageName, simpleName)
364-
if (argQualifier != null) {
365-
provider.error("Qualifier: $argQualifier can only be applied to the outer type", typeArg)
365+
val argQualifiers = typeArg.typeQualifierAnnotations().toList()
366+
if (argQualifiers.size > 1) {
367+
provider.error("Cannot apply multiple qualifiers: $argQualifiers", typeArg)
366368
}
367-
checkTypeArgs(packageName, simpleName, typeArg)
369+
checkTypeArgs(typeArg)
368370
}
369371
}
370372

371-
fun qualifier(
372-
packageName: String,
373-
simpleName: String,
374-
provider: AstProvider,
375-
element: E?,
376-
type: AstType,
377-
): AstAnnotation? {
378-
val qualifiers = (
379-
element?.annotationsAnnotatedWith(packageName, simpleName).orEmpty() +
380-
type.annotationsAnnotatedWith(packageName, simpleName)
381-
).toList()
382-
if (qualifiers.size > 1) {
383-
provider.error("Cannot apply multiple qualifiers: $qualifiers", element)
384-
}
385-
checkTypeArgs(packageName, simpleName, type)
386-
return qualifiers.firstOrNull()
387-
}
388-
// check our qualifier annotation first, then check the javax qualifier annotation. This allows you to have both
389-
// in case your in the middle of a migration.
390-
val qualifier = qualifier(
391-
QUALIFIER.packageName,
392-
QUALIFIER.simpleName,
393-
provider,
394-
element,
395-
type,
396-
)
397-
if (qualifier != null) return qualifier
398-
return if (options.enableJavaxAnnotations) {
399-
qualifier(
400-
JAVAX_QUALIFIER.packageName,
401-
JAVAX_QUALIFIER.simpleName,
402-
provider,
403-
element,
404-
type,
405-
)
406-
} else {
407-
null
373+
val qualifiers = (element?.annotationsAnnotatedWith(QUALIFIER.packageName, QUALIFIER.simpleName,).orEmpty() +
374+
if (options.enableJavaxAnnotations) {
375+
element?.annotationsAnnotatedWith(JAVAX_QUALIFIER.packageName, JAVAX_QUALIFIER.simpleName,).orEmpty()
376+
} else {
377+
emptySequence()
378+
}).toList()
379+
380+
val qualifiersIncludingType = (qualifiers + type.typeQualifierAnnotations().toList()).distinct()
381+
if (qualifiersIncludingType.size > 1) {
382+
provider.error("Cannot apply multiple qualifiers: $qualifiersIncludingType", element)
408383
}
384+
385+
checkTypeArgs(type)
386+
return qualifiers.firstOrNull() // type qualifier is used separately in TypeKey
409387
}
410388

411389
fun AstMember.isProvider(): Boolean =

kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
123123
val containerKey = ContainerKey.MapKey(
124124
resolvedType.arguments[0],
125125
resolvedType.arguments[1],
126-
key.qualifier
126+
key.memberQualifier
127127
)
128128
addContainerType(provider, key, containerKey, member, accessor, scope, scopedComponent)
129129
} else {
@@ -133,7 +133,7 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
133133
// A -> Set<A>
134134
val returnType = member.returnTypeFor(astClass)
135135
val key = TypeKey(returnType, qualifier)
136-
val containerKey = ContainerKey.SetKey(returnType, key.qualifier)
136+
val containerKey = ContainerKey.SetKey(returnType, key.memberQualifier)
137137
addContainerType(provider, key, containerKey, member, accessor, scope, scopedComponent)
138138
} else {
139139
val returnType = member.returnTypeFor(astClass)
@@ -507,29 +507,41 @@ sealed class ContainerKey {
507507
abstract val creator: String
508508
abstract fun containerTypeKey(provider: AstProvider): TypeKey
509509

510-
data class SetKey(val type: AstType, val qualifier: AstAnnotation? = null) : ContainerKey() {
510+
data class SetKey(
511+
val typeKey: TypeKey,
512+
val qualifier: AstAnnotation? = null,
513+
) : ContainerKey() {
514+
constructor(type: AstType, qualifier: AstAnnotation? = null) : this(TypeKey(type), qualifier)
515+
511516
override val creator: String = "setOf"
512517

513518
override fun containerTypeKey(provider: AstProvider): TypeKey {
514-
return TypeKey(provider.declaredTypeOf(Set::class, type), qualifier)
519+
return TypeKey(provider.declaredTypeOf(Set::class, typeKey.type), qualifier)
515520
}
516521
}
517522

518-
data class MapKey(val key: AstType, val value: AstType, val qualifier: AstAnnotation? = null) : ContainerKey() {
523+
data class MapKey(
524+
val keyTypeKey: TypeKey,
525+
val valueTypeKey: TypeKey,
526+
val qualifier: AstAnnotation? = null,
527+
) : ContainerKey() {
528+
constructor(key: AstType, value: AstType, qualifier: AstAnnotation? = null) :
529+
this(TypeKey(key), TypeKey(value), qualifier)
530+
519531
override val creator: String = "mapOf"
520532

521533
override fun containerTypeKey(provider: AstProvider): TypeKey {
522-
return TypeKey(provider.declaredTypeOf(Map::class, key, value), qualifier)
534+
return TypeKey(provider.declaredTypeOf(Map::class, keyTypeKey.type, valueTypeKey.type), qualifier)
523535
}
524536
}
525537

526538
companion object {
527539
fun fromContainer(key: TypeKey): ContainerKey? {
528540
if (key.type.isSet()) {
529-
return SetKey(key.type.arguments[0], key.qualifier)
541+
return SetKey(key.type.arguments[0], key.memberQualifier)
530542
}
531543
if (key.type.isMap()) {
532-
return MapKey(key.type.arguments[0], key.type.arguments[1], key.qualifier)
544+
return MapKey(key.type.arguments[0], key.type.arguments[1], key.memberQualifier)
533545
}
534546
return null
535547
}

kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeKey.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package me.tatarka.inject.compiler
33
import me.tatarka.kotlin.ast.AstAnnotation
44
import me.tatarka.kotlin.ast.AstType
55

6-
class TypeKey(val type: AstType, val qualifier: AstAnnotation? = null) {
6+
class TypeKey(val type: AstType, val memberQualifier: AstAnnotation? = null) {
7+
8+
val qualifier = memberQualifier ?: type.typeQualifierAnnotations().firstOrNull()
79

810
override fun equals(other: Any?): Boolean {
911
if (other !is TypeKey) return false
10-
return qualifier == other.qualifier && type == other.type
12+
return qualifier == other.qualifier && type == other.type &&
13+
type.arguments.eqvItr(other.type.arguments, ::qualifiersEquals)
1114
}
1215

1316
override fun hashCode(): Int {
@@ -24,4 +27,13 @@ class TypeKey(val type: AstType, val qualifier: AstAnnotation? = null) {
2427
}
2528
append(type)
2629
}.toString()
30+
31+
companion object {
32+
private fun qualifiersEquals(left: AstType, right: AstType): Boolean {
33+
val leftAnnotations = left.typeQualifierAnnotations().asIterable()
34+
val rightAnnotations = right.typeQualifierAnnotations().asIterable()
35+
return leftAnnotations.eqvItr(rightAnnotations, AstAnnotation::equals) &&
36+
left.arguments.eqvItr(right.arguments, ::qualifiersEquals)
37+
}
38+
}
2739
}

kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultResolver.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
235235
}
236236

237237
if (key.type.isLazy()) {
238-
val lKey = TypeKey(key.type.arguments[0], key.qualifier)
238+
val lKey = TypeKey(key.type.arguments[0], key.memberQualifier)
239239
return Lazy(key = lKey) {
240240
resolveOrNull(this, element = element, key = lKey) ?: return null
241241
}
@@ -282,7 +282,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
282282
private fun Context.set(key: TypeKey): TypeResult? {
283283
val innerType = key.type.arguments[0]
284284

285-
val containerKey = ContainerKey.SetKey(innerType, key.qualifier)
285+
val containerKey = ContainerKey.SetKey(innerType, key.memberQualifier)
286286
val args = types.containerArgs(containerKey)
287287
if (args.isNotEmpty()) {
288288
return Container(
@@ -295,7 +295,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
295295
}
296296

297297
if (innerType.isFunction()) {
298-
val containerKey = ContainerKey.SetKey(innerType.arguments.last(), key.qualifier)
298+
val containerKey = ContainerKey.SetKey(innerType.arguments.last(), key.memberQualifier)
299299
val args = types.containerArgs(containerKey)
300300
if (args.isEmpty()) return null
301301
return Container(
@@ -310,7 +310,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
310310
}
311311

312312
if (innerType.isLazy()) {
313-
val containerKey = ContainerKey.SetKey(innerType.arguments[0], key.qualifier)
313+
val containerKey = ContainerKey.SetKey(innerType.arguments[0], key.memberQualifier)
314314
val args = types.containerArgs(containerKey)
315315
if (args.isEmpty()) return null
316316
return Container(
@@ -328,7 +328,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
328328

329329
private fun Context.map(key: TypeKey): TypeResult? {
330330
val type = key.type.resolvedType()
331-
val containerKey = ContainerKey.MapKey(type.arguments[0], type.arguments[1], key.qualifier)
331+
val containerKey = ContainerKey.MapKey(type.arguments[0], type.arguments[1], key.memberQualifier)
332332
val args = types.containerArgs(containerKey)
333333
if (args.isEmpty()) return null
334334
return Container(
@@ -396,7 +396,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
396396
)
397397
}
398398
}
399-
val fKey = TypeKey(resolveType.arguments.last(), key.qualifier)
399+
val fKey = TypeKey(resolveType.arguments.last(), key.memberQualifier)
400400
return Function(this, args = args) { context ->
401401
resolveOrNull(context, element = element, key = fKey) ?: return null
402402
}

kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,7 @@ class FailureTest {
14371437

14381438
@ParameterizedTest
14391439
@EnumSource(Target::class)
1440-
fun fails_if_multiple_qualifier_is_applied_to_generic_type(target: Target) {
1440+
fun fails_if_type_qualifier_is_missing_in_generic_type(target: Target) {
14411441
val projectCompiler = ProjectCompiler(target, workingDir)
14421442

14431443
assertFailure {
@@ -1457,12 +1457,12 @@ class FailureTest {
14571457
abstract class MultipleQualifiersComponent {
14581458
abstract val foo: List<@MyQualifier String>
14591459
1460-
@Provides fun providesFoo(): List<@MyQualifier String> = "test"
1460+
@Provides fun providesFoo(): List<String> = listOf("test")
14611461
}
14621462
""".trimIndent()
14631463
).compile()
14641464
}.output().all {
1465-
contains("Qualifier: @MyQualifier can only be applied to the outer type")
1465+
contains("Cannot find an @Inject constructor or provider for: List<kotlin.String>")
14661466
}
14671467
}
14681468

0 commit comments

Comments
 (0)