diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeRenderer.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeRenderer.kt index fdc7a93..5770df4 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeRenderer.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeRenderer.kt @@ -40,13 +40,19 @@ import com.squareup.moshi.Types abstract class TypeRenderer { abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock - fun render(typeName: TypeName): CodeBlock { + fun render(typeName: TypeName, forceBox: Boolean = false): CodeBlock { if (typeName.isNullable) { return renderObjectType(typeName.copy(nullable = false)) } return when (typeName) { - is ClassName -> CodeBlock.of("%T::class.java", typeName) + is ClassName -> { + if (forceBox) { + renderObjectType(typeName) + } else { + CodeBlock.of("%T::class.java", typeName) + } + } is ParameterizedTypeName -> { // If it's an Array type, we shortcut this to return Types.arrayOf() @@ -88,7 +94,7 @@ abstract class TypeRenderer { else -> throw IllegalArgumentException( "Unrepresentable wildcard type. Cannot have more than one bound: $typeName") } - CodeBlock.of("%T.%L(%T::class.java)", Types::class, method, target.copy(nullable = false)) + CodeBlock.of("%T.%L(%L)", Types::class, method, render(target, forceBox = true)) } is TypeVariableName -> renderTypeVariable(typeName) @@ -111,4 +117,4 @@ abstract class TypeRenderer { else -> false } } -} \ No newline at end of file +} diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt index 2f344a3..2f21311 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt @@ -18,8 +18,13 @@ package com.squareup.moshi.kotlin.codegen import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.STAR import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.WildcardTypeName import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.classinspector.elements.ElementsClassInspector @@ -315,5 +320,33 @@ private fun String.escapeDollarSigns(): String { } private fun TypeName.unwrapTypeAlias(): TypeName { - return tag()?.type ?: this + return when (this) { + is ClassName -> { + tag()?.type ?: this + } + is ParameterizedTypeName -> { + return (rawType.unwrapTypeAlias() as ClassName).parameterizedBy(typeArguments.map { it.unwrapTypeAlias() }) + } + is TypeVariableName -> { + return copy(bounds = bounds.map { it.unwrapTypeAlias() }) + } + is WildcardTypeName -> { + // TODO Would be nice if KotlinPoet modeled these easier. + // Producer type - empty inTypes, single element outTypes + // Consumer type - single element inTypes, single ANY element outType. + return when { + this == STAR -> this + outTypes.isNotEmpty() && inTypes.isEmpty() -> { + WildcardTypeName.producerOf(outTypes[0].unwrapTypeAlias()) + .copy(nullable = isNullable, annotations = annotations) + } + inTypes.isNotEmpty() -> { + WildcardTypeName.consumerOf(inTypes[0].unwrapTypeAlias()) + .copy(nullable = isNullable, annotations = annotations) + } + else -> throw UnsupportedOperationException("Not possible.") + } + } + else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.") + } } diff --git a/kotlin/tests/pom.xml b/kotlin/tests/pom.xml index aea4a49..b549bfc 100644 --- a/kotlin/tests/pom.xml +++ b/kotlin/tests/pom.xml @@ -115,7 +115,11 @@ - -Werror + + -Xuse-experimental=kotlin.ExperimentalStdlibApi -XXLanguage:+InlineClasses diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt index d2b2011..b1a6f91 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt @@ -403,8 +403,46 @@ class DualKotlinTest(useReflection: Boolean) { var prop: Int = 0 } + + @Test fun typeAliasUnwrapping() { + val adapter = moshi + .newBuilder() + .add(Types.supertypeOf(Int::class.javaObjectType), moshi.adapter()) + .build() + .adapter() + + @Language("JSON") + val testJson = """{"simpleClass":6,"parameterized":{"value":6},"wildcardIn":{"value":6},"wildcardOut":{"value":6},"complex":{"value":[{"value":6}]}}""" + + val testValue = TypeAliasUnwrapping( + simpleClass = 6, + parameterized = TypeAliasGeneric(6), + wildcardIn = TypeAliasGeneric(6), + wildcardOut = TypeAliasGeneric(6), + complex = TypeAliasGeneric(listOf(TypeAliasGeneric(6))) + ) + assertThat(adapter.toJson(testValue)).isEqualTo(testJson) + + val result = adapter.fromJson(testJson)!! + assertThat(result).isEqualTo(testValue) + } + + @JsonClass(generateAdapter = true) + data class TypeAliasUnwrapping( + val simpleClass: TypeAlias, + val parameterized: TypeAliasGeneric, + val wildcardIn: TypeAliasGeneric, + val wildcardOut: TypeAliasGeneric, + @Suppress("REDUNDANT_PROJECTION") + val complex: TypeAliasGeneric>> + ) } +typealias TypeAlias = Int + +@JsonClass(generateAdapter = true) +data class TypeAliasGeneric(val value: T) + // Has to be outside since inline classes are only allowed on top level @JsonClass(generateAdapter = true) inline class InlineClass(val i: Int)