From ba26595f86c619878f3c0dda6b59055a98a15c77 Mon Sep 17 00:00:00 2001 From: stefankoppier Date: Mon, 1 Jul 2024 18:41:04 +0200 Subject: [PATCH] Fixed bug with automatic resolving of via nullable mappers --- .../AllMappieDefinitionsCollector.kt | 3 +- .../tech/mappie/resolving/Identifiers.kt | 2 + .../classes/ObjectMappingsConstructor.kt | 2 + .../ObjectWithNestedNonNullToNullTest.kt | 51 ++++++++ .../testing/ObjectWithNestedNullTest.kt | 115 ++++++++++++++++++ 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNonNullToNullTest.kt create mode 100644 compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNullTest.kt diff --git a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/AllMappieDefinitionsCollector.kt b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/AllMappieDefinitionsCollector.kt index 3c851b9e..9df41a7f 100644 --- a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/AllMappieDefinitionsCollector.kt +++ b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/AllMappieDefinitionsCollector.kt @@ -6,6 +6,7 @@ import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.types.typeOrFail import tech.mappie.BaseVisitor import tech.mappie.api.Mappie @@ -17,7 +18,7 @@ data class MappieDefinition( val clazz: IrClass, ) { fun fits(sourceType: IrType, targetType: IrType): Boolean = - (fromType.isAssignableFrom(sourceType) && targetType.isAssignableFrom(toType)) + (fromType.makeNullable().isAssignableFrom(sourceType) && targetType.makeNullable().isAssignableFrom(toType)) || fitsList(sourceType, targetType) || fitsSet(sourceType, targetType) diff --git a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/Identifiers.kt b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/Identifiers.kt index 359ff87e..2ab61a6f 100644 --- a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/Identifiers.kt +++ b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/Identifiers.kt @@ -6,6 +6,8 @@ val IDENTIFIER_MAPPING = Name.identifier("mapping") val IDENTIFIER_MAP = Name.identifier("map") +val IDENTIFIER_MAP_NULLABLE = Name.identifier("mapNullable") + val IDENTIFIER_MAP_LIST = Name.identifier("mapList") val IDENTIFIER_MAP_SET = Name.identifier("mapSet") diff --git a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ObjectMappingsConstructor.kt b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ObjectMappingsConstructor.kt index 0eb8ea26..89e19ecc 100644 --- a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ObjectMappingsConstructor.kt +++ b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ObjectMappingsConstructor.kt @@ -6,6 +6,7 @@ import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name import tech.mappie.resolving.* @@ -40,6 +41,7 @@ class ObjectMappingsConstructor(val targetType: IrType, val source: IrValueParam clazz == null -> null getter.returnType.isList() && target.type.isList() -> clazz.functions.firstOrNull { it.name == IDENTIFIER_MAP_LIST } getter.returnType.isSet() && target.type.isSet() -> clazz.functions.firstOrNull { it.name == IDENTIFIER_MAP_SET } + getter.returnType.isNullable() && target.type.isNullable() -> clazz.functions.firstOrNull { it.name == IDENTIFIER_MAP_NULLABLE } else -> clazz.functions.firstOrNull { it.name == IDENTIFIER_MAP } } val viaDispatchReceiver = when { diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNonNullToNullTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNonNullToNullTest.kt new file mode 100644 index 00000000..eee3d63d --- /dev/null +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNonNullToNullTest.kt @@ -0,0 +1,51 @@ +package tech.mappie.testing + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import tech.mappie.testing.compilation.KotlinCompilation +import tech.mappie.testing.compilation.KotlinCompilation.ExitCode +import tech.mappie.testing.compilation.SourceFile.Companion.kotlin +import java.io.File + +class ObjectWithNestedNonNullToNullTest { + data class Input(val text: InnerInput, val int: Int) + data class InnerInput(val value: String) + data class Output(val text: InnerOutput?, val int: Int) + data class InnerOutput(val value: String) + + @TempDir + private lateinit var directory: File + + @Test + fun `map data classes with nested null to non-null using object InnerMapper without declaring mapping should succeed`() { + KotlinCompilation(directory).apply { + sources = buildList { + add( + kotlin("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.ObjectWithNestedNonNullToNullTest.* + + class Mapper : ObjectMappie() + + object InnerMapper : ObjectMappie() + """ + ) + ) + } + }.compile { + assertThat(exitCode).isEqualTo(ExitCode.OK) + assertThat(messages).isEmpty() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(Input(InnerInput("value"), 20))) + .isEqualTo(Output(InnerOutput("value"), 20)) + } + } +} \ No newline at end of file diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNullTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNullTest.kt new file mode 100644 index 00000000..589292b5 --- /dev/null +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/ObjectWithNestedNullTest.kt @@ -0,0 +1,115 @@ +package tech.mappie.testing + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import tech.mappie.testing.compilation.KotlinCompilation +import tech.mappie.testing.compilation.KotlinCompilation.ExitCode +import tech.mappie.testing.compilation.SourceFile.Companion.kotlin +import java.io.File + +class ObjectWithNestedNullTest { + data class Input(val text: InnerInput?, val int: Int) + data class InnerInput(val value: String) + data class Output(val text: InnerOutput?, val int: Int) + data class InnerOutput(val value: String) + + @TempDir + private lateinit var directory: File + + @Test + fun `map data classes with nested non-null to non-null using object InnerMapper without declaring mapping should succeed`() { + KotlinCompilation(directory).apply { + sources = buildList { + add( + kotlin("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.ObjectWithNestedNullTest.* + + class Mapper : ObjectMappie() + + object InnerMapper : ObjectMappie() + """ + ) + ) + } + }.compile { + assertThat(exitCode).isEqualTo(ExitCode.OK) + assertThat(messages).isEmpty() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(Input(InnerInput("value"), 20))) + .isEqualTo(Output(InnerOutput("value"), 20)) + } + } + + @Test + fun `map data classes with nested null to null using object InnerMapper without declaring mapping should succeed`() { + KotlinCompilation(directory).apply { + sources = buildList { + add( + kotlin("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.ObjectWithNestedNullTest.* + + class Mapper : ObjectMappie() + + object InnerMapper : ObjectMappie() + """ + ) + ) + } + }.compile { + assertThat(exitCode).isEqualTo(ExitCode.OK) + assertThat(messages).isEmpty() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(Input(null, 20))) + .isEqualTo(Output(null, 20)) + } + } + + @Test + fun `map data classes with nested null to non-null using object InnerMapper without declaring mapping should succeed`() { + KotlinCompilation(directory).apply { + sources = buildList { + add( + kotlin("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.ObjectWithNestedNullTest.* + + class Mapper : ObjectMappie() + + object InnerMapper : ObjectMappie() + """ + ) + ) + } + }.compile { + assertThat(exitCode).isEqualTo(ExitCode.OK) + assertThat(messages).isEmpty() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(Input(null, 20))) + .isEqualTo(Output(null, 20)) + } + } +} \ No newline at end of file