Skip to content

Commit 686868f

Browse files
committed
Resolve generics for Kotlin Value Boxing inspection.
To introspect value boxing rules, we now resolve Kotlin type parameters. Closes #2986
1 parent 25a2408 commit 686868f

File tree

2 files changed

+73
-7
lines changed

2 files changed

+73
-7
lines changed

src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java

+44-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import kotlin.jvm.internal.Reflection;
2020
import kotlin.reflect.KCallable;
2121
import kotlin.reflect.KClass;
22+
import kotlin.reflect.KClassifier;
2223
import kotlin.reflect.KFunction;
2324
import kotlin.reflect.KParameter;
2425
import kotlin.reflect.KProperty;
@@ -109,7 +110,7 @@ public boolean shouldApplyBoxing(KType type, boolean optional, KParameter compon
109110

110111
KType copyType = expandUnderlyingType(type);
111112

112-
if (copyType.getClassifier()instanceof KClass<?> kc && kc.isValue() || copyType.isMarkedNullable()) {
113+
if (copyType.getClassifier() instanceof KClass<?> kc && kc.isValue() || copyType.isMarkedNullable()) {
113114
return true;
114115
}
115116

@@ -118,7 +119,7 @@ public boolean shouldApplyBoxing(KType type, boolean optional, KParameter compon
118119

119120
private static KType expandUnderlyingType(KType kotlinType) {
120121

121-
if (!(kotlinType.getClassifier()instanceof KClass<?> kc) || !kc.isValue()) {
122+
if (!(kotlinType.getClassifier() instanceof KClass<?> kc) || !kc.isValue()) {
122123
return kotlinType;
123124
}
124125

@@ -196,7 +197,43 @@ static class ValueBoxing {
196197
*/
197198
@SuppressWarnings("ConstantConditions")
198199
private ValueBoxing(BoxingRules rules, KParameter parameter) {
199-
this(rules, parameter.getType(), (KClass<?>) parameter.getType().getClassifier(), parameter.isOptional());
200+
this(rules, parameter.getType(), resolveClass(parameter.getType()), parameter.isOptional());
201+
}
202+
203+
private static KClass<?> resolveClass(KType type) {
204+
205+
if (type instanceof KClass<?> kc) {
206+
return kc;
207+
}
208+
209+
if (type instanceof KTypeParameter ktp) {
210+
return resolveClass(ktp.getUpperBounds().get(0));
211+
}
212+
213+
KClassifier classifier = type.getClassifier();
214+
215+
if (classifier != null) {
216+
return resolveClass(classifier);
217+
}
218+
219+
return JvmClassMappingKt.getKotlinClass(Object.class);
220+
}
221+
222+
private static KClass<?> resolveClass(KClassifier classifier) {
223+
224+
if (classifier instanceof KClass<?> kc) {
225+
return kc;
226+
}
227+
228+
if (classifier instanceof KTypeParameter ktp) {
229+
return resolveClass(ktp.getUpperBounds().get(0));
230+
}
231+
232+
if (classifier instanceof KType ktp) {
233+
return resolveClass(ktp);
234+
}
235+
236+
throw new UnsupportedOperationException(String.format("Unsupported KClassifier: %s", classifier));
200237
}
201238

202239
private ValueBoxing(BoxingRules rules, KType type, KClass<?> kClass, boolean optional) {
@@ -216,7 +253,7 @@ private ValueBoxing(BoxingRules rules, KType type, KClass<?> kClass, boolean opt
216253
KClass<?> nestedClass;
217254

218255
// bound flattening
219-
if (nestedType.getClassifier()instanceof KTypeParameter ktp) {
256+
if (nestedType.getClassifier() instanceof KTypeParameter ktp) {
220257
nestedClass = getUpperBound(ktp);
221258
} else {
222259
nestedClass = (KClass<?>) nestedType.getClassifier();
@@ -239,7 +276,7 @@ private static KClass<?> getUpperBound(KTypeParameter typeParameter) {
239276

240277
for (KType upperBound : typeParameter.getUpperBounds()) {
241278

242-
if (upperBound.getClassifier()instanceof KClass<?> kc) {
279+
if (upperBound.getClassifier() instanceof KClass<?> kc) {
243280
return kc;
244281
}
245282
}
@@ -249,11 +286,11 @@ private static KClass<?> getUpperBound(KTypeParameter typeParameter) {
249286

250287
static KType resolveType(KType type) {
251288

252-
if (type.getClassifier()instanceof KTypeParameter ktp) {
289+
if (type.getClassifier() instanceof KTypeParameter ktp) {
253290

254291
for (KType upperBound : ktp.getUpperBounds()) {
255292

256-
if (upperBound.getClassifier()instanceof KClass<?> kc) {
293+
if (upperBound.getClassifier() instanceof KClass<?> kc) {
257294
return upperBound;
258295
}
259296
}

src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt

+29
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.springframework.data.mapping.model;
1818
import org.assertj.core.api.Assertions.assertThat
1919
import org.junit.jupiter.api.Test
2020
import kotlin.reflect.KParameter
21+
import kotlin.reflect.full.memberFunctions
2122
import kotlin.reflect.jvm.javaConstructor
2223

2324
/**
@@ -204,6 +205,30 @@ class KotlinValueUtilsUnitTests {
204205
assertThat(pand.appliesBoxing()).isFalse
205206
}
206207

208+
@Test // GH-2986
209+
internal fun considersGenerics() {
210+
211+
val copyFunction =
212+
WithGenericsInConstructor::class.memberFunctions.first { it.name == "copy" }
213+
214+
val vh = KotlinValueUtils.getCopyValueHierarchy(
215+
copyFunction.parameters.get(1)
216+
)
217+
assertThat(vh.actualType).isEqualTo(Object::class.java)
218+
}
219+
220+
@Test // GH-2986
221+
internal fun considersGenericsWithBounds() {
222+
223+
val copyFunction =
224+
WithGenericsInConstructor::class.memberFunctions.first { it.name == "copy" }
225+
226+
val vh = KotlinValueUtils.getCopyValueHierarchy(
227+
copyFunction.parameters.get(1)
228+
)
229+
assertThat(vh.actualType).isEqualTo(Object::class.java)
230+
}
231+
207232
@Test // GH-1947
208233
internal fun inlinesGenericTypesConstructorRules() {
209234

@@ -247,5 +272,9 @@ class KotlinValueUtilsUnitTests {
247272
assertThat(recursive.appliesBoxing()).isFalse
248273
}
249274

275+
data class WithGenericsInConstructor<T>(val bar: T? = null)
276+
277+
data class WithGenericBoundInConstructor<T : CharSequence>(val bar: T? = null)
278+
250279
}
251280

0 commit comments

Comments
 (0)