diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt index 99cc29b..89ed9b6 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt @@ -35,15 +35,18 @@ open class TypeResolver { is ParameterizedTypeName -> { ParameterizedTypeName.get( typeName.rawType, *(typeName.typeArguments.map { resolve(it) }.toTypedArray())) + .asNullableIf(typeName.nullable) } is WildcardTypeName -> { when { typeName.lowerBounds.size == 1 -> { WildcardTypeName.supertypeOf(resolve(typeName.lowerBounds[0])) + .asNullableIf(typeName.nullable) } typeName.upperBounds.size == 1 -> { WildcardTypeName.subtypeOf(resolve(typeName.upperBounds[0])) + .asNullableIf(typeName.nullable) } else -> { throw IllegalArgumentException( @@ -57,4 +60,4 @@ open class TypeResolver { else -> throw IllegalArgumentException("Unrepresentable type: $typeName") } } -} \ No newline at end of file +} diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt index f5d9580..fd4c6d1 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt @@ -26,3 +26,7 @@ internal fun TypeName.rawType(): ClassName { else -> throw IllegalArgumentException("Cannot get raw type from $this") } } + +internal fun TypeName.asNullableIf(condition: Boolean): TypeName { + return if (condition) asNullable() else this +} diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/metadata.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/metadata.kt index 1109cfb..d26170b 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/metadata.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/metadata.kt @@ -72,15 +72,17 @@ internal fun Type.asTypeName( if (hasFlexibleUpperBound()) { return WildcardTypeName.subtypeOf( flexibleUpperBound.asTypeName(nameResolver, getTypeParameter, resolveAliases)) + .asNullableIf(nullable) } else if (hasOuterType()) { return WildcardTypeName.supertypeOf( outerType.asTypeName(nameResolver, getTypeParameter, resolveAliases)) + .asNullableIf(nullable) } val realType = when { hasTypeParameter() -> return getTypeParameter(typeParameter) .asTypeName(nameResolver, getTypeParameter, resolveAliases) - .let { if (nullable) it.asNullable() else it } + .asNullableIf(nullable) hasTypeParameterName() -> typeParameterName hasAbbreviatedType() && !resolveAliases -> abbreviatedType.typeAliasName else -> className @@ -91,43 +93,36 @@ internal fun Type.asTypeName( .replace("/", ".")) if (argumentList.isNotEmpty()) { - val remappedArgs: Array = argumentList.map { - val projection = if (it.hasProjection()) { - it.projection + val remappedArgs: Array = argumentList.map { argumentType -> + val nullableProjection = if (argumentType.hasProjection()) { + argumentType.projection } else null - if (it.hasType()) { - it.type.asTypeName(nameResolver, getTypeParameter, resolveAliases) - .let { typeName -> - projection?.let { - when (it) { - Type.Argument.Projection.IN -> WildcardTypeName.supertypeOf( - typeName) + if (argumentType.hasType()) { + argumentType.type.asTypeName(nameResolver, getTypeParameter, resolveAliases) + .let { argumentTypeName -> + nullableProjection?.let { projection -> + when (projection) { + Type.Argument.Projection.IN -> WildcardTypeName.supertypeOf(argumentTypeName) Type.Argument.Projection.OUT -> { - if (typeName == ANY) { + if (argumentTypeName == ANY) { // This becomes a *, which we actually don't want here. // List works with List<*>, but List<*> doesn't work with List - typeName + argumentTypeName } else { - WildcardTypeName.subtypeOf(typeName) + WildcardTypeName.subtypeOf(argumentTypeName) } } - Type.Argument.Projection.STAR -> WildcardTypeName.subtypeOf( - ANY) + Type.Argument.Projection.STAR -> WildcardTypeName.subtypeOf(ANY) Type.Argument.Projection.INV -> TODO("INV projection is unsupported") } - } ?: typeName + } ?: argumentTypeName } } else { WildcardTypeName.subtypeOf(ANY) } }.toTypedArray() - typeName = ParameterizedTypeName.get( - typeName as ClassName, *remappedArgs) + typeName = ParameterizedTypeName.get(typeName as ClassName, *remappedArgs) } - if (nullable) { - typeName = typeName.asNullable() - } - - return typeName + return typeName.asNullableIf(nullable) } diff --git a/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/TypeResolverTest.kt b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/TypeResolverTest.kt new file mode 100644 index 0000000..f81f27b --- /dev/null +++ b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/TypeResolverTest.kt @@ -0,0 +1,36 @@ +package com.squareup.moshi + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.WildcardTypeName +import com.squareup.kotlinpoet.asClassName +import org.junit.Test + +class TypeResolverTest { + + private val resolver = TypeResolver() + + @Test + fun ensureClassNameNullabilityIsPreserved() { + assertThat(resolver.resolve(Int::class.asClassName().asNullable()).nullable).isTrue() + } + + @Test + fun ensureParameterizedNullabilityIsPreserved() { + val nullableTypeName = ParameterizedTypeName.get( + List::class.asClassName(), + String::class.asClassName()) + .asNullable() + + assertThat(resolver.resolve(nullableTypeName).nullable).isTrue() + } + + @Test + fun ensureWildcardNullabilityIsPreserved() { + val nullableTypeName = WildcardTypeName.subtypeOf(List::class.asClassName()) + .asNullable() + + assertThat(resolver.resolve(nullableTypeName).nullable).isTrue() + } + +}