Fix handling of typealiases in non-classname envs + wildcard fix… (#987)

* Correctly render non-classname wildcard types

Resolves #984
Resolves #985

* Add thorough typealias test

* Expand unwrapTypeAlias to parameterized and wildcard types

Resolves #983

* Disable Werror for now
This commit is contained in:
Zac Sweers
2019-10-30 21:15:28 -04:00
committed by GitHub
parent ac789070e8
commit 092175ae90
4 changed files with 87 additions and 6 deletions

View File

@@ -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)

View File

@@ -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<TypeNameAliasTag>()?.type ?: this
return when (this) {
is ClassName -> {
tag<TypeNameAliasTag>()?.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.")
}
}

View File

@@ -115,7 +115,11 @@
</executions>
<configuration>
<args>
<arg>-Werror</arg>
<!--
Disabled for now because we generate redundant `out` variance for some generics,
but there's no way for us to know when it's redundant.
-->
<!-- <arg>-Werror</arg>-->
<arg>-Xuse-experimental=kotlin.ExperimentalStdlibApi</arg>
<arg>-XXLanguage:+InlineClasses</arg>
</args>

View File

@@ -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<Int>())
.build()
.adapter<TypeAliasUnwrapping>()
@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<TypeAlias>,
val wildcardIn: TypeAliasGeneric<in TypeAlias>,
val wildcardOut: TypeAliasGeneric<out TypeAlias>,
@Suppress("REDUNDANT_PROJECTION")
val complex: TypeAliasGeneric<List<out TypeAliasGeneric<in TypeAlias>>>
)
}
typealias TypeAlias = Int
@JsonClass(generateAdapter = true)
data class TypeAliasGeneric<T>(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)