Browse Source

Invalid Nullness information for Kotlin properties

This commit adds support for Kotlin properties to Nullness
forMethodReturnType and forParameter methods.

Closes gh-35419
pull/35447/head
Sébastien Deleuze 3 months ago
parent
commit
d218b0899a
  1. 56
      spring-core/src/main/java/org/springframework/core/Nullness.java
  2. 32
      spring-core/src/test/kotlin/org/springframework/core/NullnessKotlinTests.kt

56
spring-core/src/main/java/org/springframework/core/Nullness.java

@ -27,9 +27,13 @@ import java.lang.reflect.Parameter; @@ -27,9 +27,13 @@ import java.lang.reflect.Parameter;
import java.util.Objects;
import java.util.function.Predicate;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.KProperty;
import kotlin.reflect.KType;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.ReflectJvmMapping;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
@ -181,8 +185,23 @@ public enum Nullness { @@ -181,8 +185,23 @@ public enum Nullness {
public static Nullness forMethodReturnType(Method method) {
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
if (function != null && ReflectJvmMapping.getJavaType(function.getReturnType()) != void.class) {
return (function.getReturnType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
if (function == null) {
String methodName = method.getName();
if (methodName.startsWith("get")) {
String propertyName = accessorToPropertyName(methodName);
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
for (KProperty<?> property : KClasses.getMemberProperties(kClass)) {
if (property.getName().equals(propertyName)) {
return (property.getReturnType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
}
}
}
}
else {
KType type = function.getReturnType();
if (ReflectJvmMapping.getJavaType(type) != void.class) {
return (type.isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
}
}
return Nullness.UNSPECIFIED;
}
@ -200,12 +219,23 @@ public enum Nullness { @@ -200,12 +219,23 @@ public enum Nullness {
KParameter.Kind.INSTANCE.equals(p.getKind()));
}
if (function == null) {
return Nullness.UNSPECIFIED;
String methodName = executable.getName();
if (methodName.startsWith("set")) {
String propertyName = accessorToPropertyName(methodName);
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(executable.getDeclaringClass());
for (KProperty<?> property : KClasses.getMemberProperties(kClass)) {
if (property.getName().equals(propertyName)) {
return (property.getReturnType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
}
}
}
}
int i = 0;
for (KParameter kParameter : function.getParameters()) {
if (predicate.test(kParameter) && parameterIndex == i++) {
return (kParameter.getType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
else {
int i = 0;
for (KParameter kParameter : function.getParameters()) {
if (predicate.test(kParameter) && parameterIndex == i++) {
return (kParameter.getType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
}
}
}
return Nullness.UNSPECIFIED;
@ -213,10 +243,16 @@ public enum Nullness { @@ -213,10 +243,16 @@ public enum Nullness {
public static Nullness forField(Field field) {
KProperty<?> property = ReflectJvmMapping.getKotlinProperty(field);
if (property != null && property.getReturnType().isMarkedNullable()) {
return Nullness.NULLABLE;
if (property != null) {
return (property.getReturnType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL);
}
return Nullness.NON_NULL;
return Nullness.UNSPECIFIED;
}
private static String accessorToPropertyName(String method) {
char[] methodNameChars = method.toCharArray();
methodNameChars[3] = Character.toLowerCase(methodNameChars[3]);
return new String(methodNameChars, 3, methodNameChars.length - 3);
}
}

32
spring-core/src/test/kotlin/org/springframework/core/NullnessKotlinTests.kt

@ -79,6 +79,34 @@ class NullnessKotlinTests { @@ -79,6 +79,34 @@ class NullnessKotlinTests {
Assertions.assertThat(nullness).isEqualTo(Nullness.NON_NULL)
}
@Test
fun nullableDataClassGetter() {
val method = NullableName::class.java.getDeclaredMethod("getName")
val nullness = Nullness.forMethodReturnType(method)
Assertions.assertThat(nullness).isEqualTo(Nullness.NULLABLE)
}
@Test
fun nonNullableDataClassGetter() {
val method = NonNullableName::class.java.getDeclaredMethod("getName")
val nullness = Nullness.forMethodReturnType(method)
Assertions.assertThat(nullness).isEqualTo(Nullness.NON_NULL)
}
@Test
fun nullableDataClassSetter() {
val method = NullableName::class.java.getDeclaredMethod("setName", String::class.java)
val nullness = Nullness.forParameter(method.parameters[0])
Assertions.assertThat(nullness).isEqualTo(Nullness.NULLABLE)
}
@Test
fun nonNullableDataClassSetter() {
val method = NonNullableName::class.java.getDeclaredMethod("setName", String::class.java)
val nullness = Nullness.forParameter(method.parameters[0])
Assertions.assertThat(nullness).isEqualTo(Nullness.NON_NULL)
}
@Suppress("unused_parameter")
fun nullable(nullable: String?): String? = "foo"
@ -88,4 +116,8 @@ class NullnessKotlinTests { @@ -88,4 +116,8 @@ class NullnessKotlinTests {
fun unit() {
}
data class NullableName(var name: String?)
data class NonNullableName(var name: String)
}
Loading…
Cancel
Save