Make nullability flexible in ConverterFactory#getConverter

This commits udpates ConverterFactory#getConverter to accept both
nullable and non-null T in the Converter return value.

Flexible nullability at ConverterFactory type level would have been
ideal, but is not possible due to how Kotlin deals with Class<T> when
T is nullable.

See gh-36063
This commit is contained in:
Sébastien Deleuze
2025-12-30 16:07:39 +01:00
parent b8ed4f2ba8
commit 96eee8c8b1
2 changed files with 22 additions and 6 deletions
@@ -38,6 +38,6 @@ public interface ConverterFactory<S, R> {
* @param targetType the target type to convert to
* @return a converter from S to T
*/
<T extends R> Converter<S, @Nullable T> getConverter(Class<T> targetType);
<T extends R> Converter<S, ? extends @Nullable T> getConverter(Class<T> targetType);
}
@@ -22,18 +22,27 @@ import kotlin.reflect.full.primaryConstructor
/**
* @author Brian Clozel
* @author Sebastien Deleuze
*/
class ConverterFactoryNullnessTests {
@Test
fun converterFactoryWithNullableTypes() {
val factory = StringToIdConverterFactory
val factory = StringToNullableIdConverterFactory
val userIdConverter = factory.getConverter(UserId::class.java)
val userIdConverter: Converter<String, UserId?> = factory.getConverter(UserId::class.java)
assertThat(userIdConverter.convert("42")).isEqualTo(UserId("42"))
}
object StringToIdConverterFactory : ConverterFactory<String, Id> {
@Test
fun converterFactoryWithNonNullableTypes() {
val factory = StringToIdConverterFactory
val userIdConverter: Converter<String, UserId> = factory.getConverter(UserId::class.java)
assertThat(userIdConverter.convert("42")).isEqualTo(UserId("42"))
}
object StringToNullableIdConverterFactory : ConverterFactory<String, Id> {
override fun <T : Id> getConverter(targetType: Class<T>): Converter<String, T?> {
val constructor = checkNotNull(targetType.kotlin.primaryConstructor)
return Converter { source ->
@@ -42,12 +51,19 @@ class ConverterFactoryNullnessTests {
}
}
object StringToIdConverterFactory : ConverterFactory<String, Id> {
override fun <T : Id> getConverter(targetType: Class<T>): Converter<String, T> {
val constructor = checkNotNull(targetType.kotlin.primaryConstructor)
return Converter { source ->
constructor.call(source)
}
}
}
abstract class Id {
abstract val value: String
}
data class UserId(override val value: String) : Id()
data class ProductId(override val value: String) : Id()
}