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 58d2bc4..ac09a75 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 @@ -18,6 +18,7 @@ package com.squareup.moshi.kotlin.codegen.api import com.squareup.kotlinpoet.ARRAY import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.CodeBlock.Companion import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.INT @@ -158,6 +159,22 @@ internal class AdapterGenerator( if (typeVariables.isNotEmpty()) { result.addTypeVariables(typeVariables.map { it.stripTypeVarVariance() as TypeVariableName }) + // require(types.size == 1) { + // "TypeVariable mismatch: Expecting 1 type(s) for generic type variables [T], but received ${types.size} with values $types" + // } + result.addInitializerBlock(CodeBlock.builder() + .beginControlFlow("require(types.size == %L)", typeVariables.size) + .addStatement( + "buildString·{·append(%S).append(%L).append(%S).append(%S).append(%S).append(%L)·}", + "TypeVariable mismatch: Expecting ", + typeVariables.size, + " ${if (typeVariables.size == 1) "type" else "types"} for generic type variables [", + typeVariables.joinToString(", ") { it.name }, + "], but received ", + "${typesParam.name}.size" + ) + .endControlFlow() + .build()) } // TODO make this configurable. Right now it just matches the source model 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 a5d28c5..46601cf 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 @@ -1220,6 +1220,30 @@ class GeneratedAdaptersTest { val propertyWithAnnotatedType: @TypeAnnotation String = "", val generic: List<@TypeAnnotation String> ) + + @Test fun typesSizeCheckMessages_noArgs() { + try { + moshi.adapter(MultipleGenerics::class.java) + fail("Should have failed to construct the adapter due to missing generics") + } catch (e: RuntimeException) { + assertThat(e).hasMessage("Failed to find the generated JsonAdapter constructor for 'class com.squareup.moshi.kotlin.codegen.GeneratedAdaptersTest\$MultipleGenerics'. Suspiciously, the type was not parameterized but the target class 'com.squareup.moshi.kotlin.codegen.GeneratedAdaptersTest_MultipleGenericsJsonAdapter' is generic. Consider using Types#newParameterizedType() to define these missing type variables.") + } + } + + @Test fun typesSizeCheckMessages_wrongNumberOfArgs() { + try { + GeneratedAdaptersTest_MultipleGenericsJsonAdapter( + moshi, + arrayOf(String::class.java) + ) + fail("Should have failed to construct the adapter due to wrong number of generics") + } catch (e: IllegalArgumentException) { + assertThat(e).hasMessage("TypeVariable mismatch: Expecting 4 types for generic type variables [A, B, C, D], but received 1") + } + } + + @JsonClass(generateAdapter = true) + data class MultipleGenerics(val prop: String) } // Regression test for https://github.com/square/moshi/issues/1022 diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.java b/moshi/src/main/java/com/squareup/moshi/internal/Util.java index 423b289..b936764 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.java +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.java @@ -517,9 +517,10 @@ public final class Util { return null; } String adapterClassName = Types.generatedJsonAdapterName(rawType.getName()); + Class> adapterClass = null; try { - @SuppressWarnings("unchecked") // We generate types to match. - Class> adapterClass = (Class>) + //noinspection unchecked - We generate types to match. + adapterClass = (Class>) Class.forName(adapterClassName, true, rawType.getClassLoader()); Constructor> constructor; Object[] args; @@ -547,16 +548,24 @@ public final class Util { return constructor.newInstance(args).nullSafe(); } catch (ClassNotFoundException e) { throw new RuntimeException( - "Failed to find the generated JsonAdapter class for " + rawType, e); + "Failed to find the generated JsonAdapter class for " + type, e); } catch (NoSuchMethodException e) { - throw new RuntimeException( - "Failed to find the generated JsonAdapter constructor for " + rawType, e); + if (!(type instanceof ParameterizedType) && adapterClass.getTypeParameters().length != 0) { + throw new RuntimeException( + "Failed to find the generated JsonAdapter constructor for '" + type + + "'. Suspiciously, the type was not parameterized but the target class '" + + adapterClass.getCanonicalName() + "' is generic. Consider using " + + "Types#newParameterizedType() to define these missing type variables.", e); + } else { + throw new RuntimeException( + "Failed to find the generated JsonAdapter constructor for " + type, e); + } } catch (IllegalAccessException e) { throw new RuntimeException( - "Failed to access the generated JsonAdapter for " + rawType, e); + "Failed to access the generated JsonAdapter for " + type, e); } catch (InstantiationException e) { throw new RuntimeException( - "Failed to instantiate the generated JsonAdapter for " + rawType, e); + "Failed to instantiate the generated JsonAdapter for " + type, e); } catch (InvocationTargetException e) { throw rethrowCause(e); }