From 686868fe3c6a6d47b031d200622dcafffe9de889 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 14 Dec 2023 14:56:27 +0100 Subject: [PATCH] Resolve generics for Kotlin Value Boxing inspection. To introspect value boxing rules, we now resolve Kotlin type parameters. Closes #2986 --- .../data/mapping/model/KotlinValueUtils.java | 51 ++++++++++++++++--- .../model/KotlinValueUtilsUnitTests.kt | 29 +++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java b/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java index 3972b3d3b..d15ffdf0b 100644 --- a/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java +++ b/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java @@ -19,6 +19,7 @@ import kotlin.jvm.JvmClassMappingKt; import kotlin.jvm.internal.Reflection; import kotlin.reflect.KCallable; import kotlin.reflect.KClass; +import kotlin.reflect.KClassifier; import kotlin.reflect.KFunction; import kotlin.reflect.KParameter; import kotlin.reflect.KProperty; @@ -109,7 +110,7 @@ class KotlinValueUtils { KType copyType = expandUnderlyingType(type); - if (copyType.getClassifier()instanceof KClass kc && kc.isValue() || copyType.isMarkedNullable()) { + if (copyType.getClassifier() instanceof KClass kc && kc.isValue() || copyType.isMarkedNullable()) { return true; } @@ -118,7 +119,7 @@ class KotlinValueUtils { private static KType expandUnderlyingType(KType kotlinType) { - if (!(kotlinType.getClassifier()instanceof KClass kc) || !kc.isValue()) { + if (!(kotlinType.getClassifier() instanceof KClass kc) || !kc.isValue()) { return kotlinType; } @@ -196,7 +197,43 @@ class KotlinValueUtils { */ @SuppressWarnings("ConstantConditions") private ValueBoxing(BoxingRules rules, KParameter parameter) { - this(rules, parameter.getType(), (KClass) parameter.getType().getClassifier(), parameter.isOptional()); + this(rules, parameter.getType(), resolveClass(parameter.getType()), parameter.isOptional()); + } + + private static KClass resolveClass(KType type) { + + if (type instanceof KClass kc) { + return kc; + } + + if (type instanceof KTypeParameter ktp) { + return resolveClass(ktp.getUpperBounds().get(0)); + } + + KClassifier classifier = type.getClassifier(); + + if (classifier != null) { + return resolveClass(classifier); + } + + return JvmClassMappingKt.getKotlinClass(Object.class); + } + + private static KClass resolveClass(KClassifier classifier) { + + if (classifier instanceof KClass kc) { + return kc; + } + + if (classifier instanceof KTypeParameter ktp) { + return resolveClass(ktp.getUpperBounds().get(0)); + } + + if (classifier instanceof KType ktp) { + return resolveClass(ktp); + } + + throw new UnsupportedOperationException(String.format("Unsupported KClassifier: %s", classifier)); } private ValueBoxing(BoxingRules rules, KType type, KClass kClass, boolean optional) { @@ -216,7 +253,7 @@ class KotlinValueUtils { KClass nestedClass; // bound flattening - if (nestedType.getClassifier()instanceof KTypeParameter ktp) { + if (nestedType.getClassifier() instanceof KTypeParameter ktp) { nestedClass = getUpperBound(ktp); } else { nestedClass = (KClass) nestedType.getClassifier(); @@ -239,7 +276,7 @@ class KotlinValueUtils { for (KType upperBound : typeParameter.getUpperBounds()) { - if (upperBound.getClassifier()instanceof KClass kc) { + if (upperBound.getClassifier() instanceof KClass kc) { return kc; } } @@ -249,11 +286,11 @@ class KotlinValueUtils { static KType resolveType(KType type) { - if (type.getClassifier()instanceof KTypeParameter ktp) { + if (type.getClassifier() instanceof KTypeParameter ktp) { for (KType upperBound : ktp.getUpperBounds()) { - if (upperBound.getClassifier()instanceof KClass kc) { + if (upperBound.getClassifier() instanceof KClass kc) { return upperBound; } } diff --git a/src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt b/src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt index 46cc688db..509cc9a7d 100644 --- a/src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt @@ -18,6 +18,7 @@ package org.springframework.data.mapping.model; import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import kotlin.reflect.KParameter +import kotlin.reflect.full.memberFunctions import kotlin.reflect.jvm.javaConstructor /** @@ -204,6 +205,30 @@ class KotlinValueUtilsUnitTests { assertThat(pand.appliesBoxing()).isFalse } + @Test // GH-2986 + internal fun considersGenerics() { + + val copyFunction = + WithGenericsInConstructor::class.memberFunctions.first { it.name == "copy" } + + val vh = KotlinValueUtils.getCopyValueHierarchy( + copyFunction.parameters.get(1) + ) + assertThat(vh.actualType).isEqualTo(Object::class.java) + } + + @Test // GH-2986 + internal fun considersGenericsWithBounds() { + + val copyFunction = + WithGenericsInConstructor::class.memberFunctions.first { it.name == "copy" } + + val vh = KotlinValueUtils.getCopyValueHierarchy( + copyFunction.parameters.get(1) + ) + assertThat(vh.actualType).isEqualTo(Object::class.java) + } + @Test // GH-1947 internal fun inlinesGenericTypesConstructorRules() { @@ -247,5 +272,9 @@ class KotlinValueUtilsUnitTests { assertThat(recursive.appliesBoxing()).isFalse } + data class WithGenericsInConstructor(val bar: T? = null) + + data class WithGenericBoundInConstructor(val bar: T? = null) + }