diff --git a/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt b/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt index 5311a5c1a95..e444f60b880 100644 --- a/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt +++ b/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt @@ -463,7 +463,6 @@ internal open class KotlinClassElement( override fun isTypeVariable() = typeVariable - @OptIn(KspExperimental::class) override fun isAssignable(type: String): Boolean { val otherDeclaration = visitorContext.resolver.getClassDeclarationByName(type) if (otherDeclaration != null) { @@ -481,11 +480,12 @@ internal open class KotlinClassElement( if (thisFullName == otherFullName) { return true } - val otherKotlinType = otherDeclaration.asStarProjectedType() - if (otherKotlinType == kotlinType) { + val otherKotlinType = otherDeclaration.asStarProjectedType().makeNullable() + val kotlinTypeNullable = kotlinType.makeNullable() + if (otherKotlinType == kotlinTypeNullable) { return true } - if (otherKotlinType.isAssignableFrom(kotlinType)) { + if (otherKotlinType.isAssignableFrom(kotlinTypeNullable)) { return true } } @@ -499,12 +499,12 @@ internal open class KotlinClassElement( visitorContext.resolver.getKSNameFromString(type) ) ?: return false val kotlinClassByName = visitorContext.resolver.getKotlinClassByName(kotlinName) ?: return false - return kotlinClassByName.asStarProjectedType().isAssignableFrom(kotlinType.starProjection()) + return kotlinClassByName.asStarProjectedType().makeNullable().isAssignableFrom(kotlinType.starProjection().makeNullable()) } override fun isAssignable(type: ClassElement): Boolean { if (type is KotlinClassElement) { - return type.kotlinType.isAssignableFrom(kotlinType) + return type.kotlinType.starProjection().makeNullable().isAssignableFrom(kotlinType.starProjection().makeNullable()) } return super.isAssignable(type) } diff --git a/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/inject/ast/ClassElementSpec.groovy b/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/inject/ast/ClassElementSpec.groovy index f792783d5d2..cab2aa738df 100644 --- a/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/inject/ast/ClassElementSpec.groovy +++ b/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/inject/ast/ClassElementSpec.groovy @@ -1872,19 +1872,54 @@ class Test { fun method2() : java.util.List? { return null } + + @Executable + fun method3() : kotlin.collections.List? { + return listOf() + } + } ''', ce -> { return ce.findMethod("method1").get().getReturnType().isAssignable(Iterable.class) && ce.findMethod("method2").get().getReturnType().isAssignable(Iterable.class) + && ce.findMethod("method3").get().getReturnType().isAssignable(Iterable.class) && ((KotlinClassElement) ce.findMethod("method1").get().getReturnType()).isAssignable2(Iterable.class.name) && ((KotlinClassElement) ce.findMethod("method2").get().getReturnType()).isAssignable2(Iterable.class.name) + && ((KotlinClassElement) ce.findMethod("method3").get().getReturnType()).isAssignable2(Iterable.class.name) }) expect: isAssignable } + void "test type isAssignable between nullable and not nullable"() { + when: + boolean isAssignable = buildClassElementMapped('test.Cart', ''' +package test + +data class CartItem( + val id: Long?, + val name: String, + val cart: Cart? +) { + constructor(name: String) : this(null, name, null) +} + +data class Cart( + val id: Long?, + val items: List? +) { + + constructor(items: List) : this(null, items) + + fun cartItemsNotNullable() : List = listOf() +} + +''', cl -> cl.getPrimaryConstructor().get().parameters[1].getType().isAssignable(cl.findMethod("cartItemsNotNullable").get().getReturnType())) + then: + isAssignable + } private void assertListGenericArgument(ClassElement type, Closure cl) { def arg1 = type.getAllTypeArguments().get(List.class.name).get("E") diff --git a/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/visitor/BeanIntrospectionSpec.groovy b/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/visitor/BeanIntrospectionSpec.groovy index c8138a20684..78d56e3ee53 100644 --- a/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/visitor/BeanIntrospectionSpec.groovy +++ b/inject-kotlin/src/test/groovy/io/micronaut/kotlin/processing/visitor/BeanIntrospectionSpec.groovy @@ -2166,4 +2166,36 @@ class Holder( animal instanceof GenericPlaceholder animal.isTypeVariable() } + + void "test list property"() { + given: + BeanIntrospection introspection = buildBeanIntrospection('test.Cart', ''' +package test +import io.micronaut.core.annotation.Introspected + +@io.micronaut.core.annotation.Introspected +data class CartItem( + val id: Long?, + val name: String, + val cart: Cart? +) { + constructor(name: String) : this(null, name, null) +} + +@io.micronaut.core.annotation.Introspected +data class Cart( + val id: Long?, + val items: List? +) { + + constructor(items: List) : this(null, items) + + fun cartItemsNotNullable() : List = listOf() +} + ''') + def bean = introspection.instantiate(1L, new ArrayList()) + bean = introspection.getProperty("items").get().withValue(bean, new ArrayList()) + expect: + bean + } }