From 3bc47519abb950dc09d8a0786e0bb05744c73f4d Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 13 Mar 2021 20:47:52 -0500 Subject: [PATCH] Fix reading property function types (#1311) --- .../kotlin/codegen/api/AdapterGenerator.kt | 8 +++++-- .../moshi/kotlin/codegen/api/kotlintypes.kt | 22 +++++++++++++++++++ .../squareup/moshi/kotlin/codegen/metadata.kt | 2 ++ .../kotlin/codegen/GeneratedAdaptersTest.kt | 20 +++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt index ce6cfed..8326f6d 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt @@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin.codegen.api import com.squareup.kotlinpoet.ARRAY import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -82,9 +83,12 @@ internal class AdapterGenerator( // like for stylistic reasons. "LocalVariableName", // KotlinPoet always generates explicit public modifiers for public members. - "RedundantVisibilityModifier" + "RedundantVisibilityModifier", + // For LambdaTypeNames we have to import kotlin.functions.* types + "PLATFORM_CLASS_MAPPED_TO_KOTLIN" ).let { suppressions -> AnnotationSpec.builder(Suppress::class) + .useSiteTarget(FILE) .addMember( suppressions.indices.joinToString { "%S" }, *suppressions @@ -175,6 +179,7 @@ internal class AdapterGenerator( val generatedAdapter = generateType().let(typeHook) val result = FileSpec.builder(className.packageName, adapterName) result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.") + result.addAnnotation(COMMON_SUPPRESS) result.addType(generatedAdapter) return PreparedAdapter(result.build(), generatedAdapter.createProguardRule()) } @@ -224,7 +229,6 @@ internal class AdapterGenerator( private fun generateType(): TypeSpec { val result = TypeSpec.classBuilder(adapterName) - .addAnnotation(COMMON_SUPPRESS) result.superclass(jsonAdapterTypeName) diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/kotlintypes.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/kotlintypes.kt index 94a5c86..dc18524 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/kotlintypes.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/kotlintypes.kt @@ -27,6 +27,7 @@ import com.squareup.kotlinpoet.FLOAT import com.squareup.kotlinpoet.INT import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.LONG +import com.squareup.kotlinpoet.LambdaTypeName import com.squareup.kotlinpoet.NOTHING import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy @@ -48,6 +49,18 @@ internal fun TypeName.findRawType(): ClassName? { return when (this) { is ClassName -> this is ParameterizedTypeName -> rawType + is LambdaTypeName -> { + var count = parameters.size + if (receiver != null) { + count++ + } + val functionSimpleName = if (count >= 23) { + "FunctionN" + } else { + "Function$count" + } + ClassName("kotlin.jvm.functions", functionSimpleName) + } else -> null } } @@ -93,6 +106,7 @@ internal fun TypeName.asTypeBlock(): CodeBlock { val bound = bounds.firstOrNull() ?: ANY return bound.asTypeBlock() } + is LambdaTypeName -> return rawType().asTypeBlock() is ClassName -> { // Check against the non-nullable version for equality, but we'll keep the nullability in // consideration when creating the CodeBlock if needed. @@ -164,6 +178,14 @@ internal fun WildcardTypeName.deepCopy(transform: (TypeName) -> TypeName): TypeN } } +internal fun LambdaTypeName.deepCopy(transform: (TypeName) -> TypeName): TypeName { + return LambdaTypeName.get( + receiver?.let(transform), + parameters.map { it.toBuilder(type = transform(it.type)).build() }, + transform(returnType) + ).copy(nullable = isNullable, annotations = annotations, suspending = isSuspending) +} + internal interface TypeVariableResolver { val parametersMap: Map operator fun get(index: String): TypeVariableName 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 3531e4d..e9ff920 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,6 +18,7 @@ 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.LambdaTypeName import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec @@ -530,6 +531,7 @@ internal fun TypeName.unwrapTypeAlias(): TypeName { is ParameterizedTypeName -> deepCopy(TypeName::unwrapTypeAlias) is TypeVariableName -> deepCopy(transform = TypeName::unwrapTypeAlias) is WildcardTypeName -> deepCopy(TypeName::unwrapTypeAlias) + is LambdaTypeName -> deepCopy(TypeName::unwrapTypeAlias) else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.") } } diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt index 042b8fd..6a0bd91 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt @@ -1379,6 +1379,26 @@ class GeneratedAdaptersTest { @JsonClass(generateAdapter = true) data class MultipleGenerics(val prop: String) + + @Test fun functionPropertyTypes() { + val adapter = moshi.adapter() + val json = "{\"id\":\"value\"}" + assertThat(adapter.fromJson(json)).isEqualTo(LambdaTypeNames("value")) + } + + // Regression test for https://github.com/square/moshi/issues/1265 + @JsonClass(generateAdapter = true) + data class LambdaTypeNames( + val id: String, + @Transient + val simple: ((String) -> Boolean)? = null, + // Receivers count as the first param, just annotated with a special annotation to indicate it's a receiver + @Transient + val receiver: (String.(String) -> Boolean)? = null, + // Tests that we use `FunctionN` since it has more than 23 params + @Transient + val arity: (String.(String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String) -> Boolean)? = null, + ) } // Regression test for https://github.com/square/moshi/issues/1277