From e3a3b009851eaeb1435aafc7f6a53254ca6bea30 Mon Sep 17 00:00:00 2001 From: Abel Date: Mon, 18 Nov 2024 14:28:49 +0100 Subject: [PATCH 1/4] method references supported --- .../classes/ExplicitClassMappingCollector.kt | 10 ++- .../classes/sources/ClassMappingSource.kt | 9 ++- .../testing/objects/MethodReferenceTest.kt | 75 +++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt diff --git a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt index 0e40f1a..6668e77 100644 --- a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt +++ b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt @@ -54,7 +54,7 @@ private class ClassMappingStatementCollector(private val context: ResolverContex } IDENTIFIER_FROM_EXPRESSION -> { val target = expression.extensionReceiver!!.accept(TargetNameCollector(context), data) - target to ExpressionMappingSource(expression.valueArguments.first() as IrFunctionExpression) + target to ExpressionMappingSource(expression.valueArguments.first() as IrExpression) } IDENTIFIER_VIA -> { expression.dispatchReceiver!!.accept(data).let { (name, source) -> @@ -66,7 +66,13 @@ private class ClassMappingStatementCollector(private val context: ResolverContex IDENTIFIER_TRANSFORM -> { expression.dispatchReceiver!!.accept(data).let { (name, source) -> name to (source as ExplicitPropertyMappingSource).copy( - transformation = PropertyMappingTransformTranformation(expression.valueArguments.first()!! as IrFunctionExpression) + transformation = expression.valueArguments.first().let { + when (it) { + is IrFunctionExpression -> PropertyMappingTransformTranformation(it) + is IrFunctionReference -> PropertyMappingTransformTranformation(it) + else -> throw MappiePanicException("Unexpected expression type: ${expression.type}") + } + } ) } } diff --git a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/sources/ClassMappingSource.kt b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/sources/ClassMappingSource.kt index e964f02..16f019e 100644 --- a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/sources/ClassMappingSource.kt +++ b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/sources/ClassMappingSource.kt @@ -6,6 +6,7 @@ import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionReference import org.jetbrains.kotlin.ir.expressions.IrPropertyReference import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.name.Name @@ -85,10 +86,12 @@ sealed interface PropertyMappingTransformation { val type: IrType } -data class PropertyMappingTransformTranformation( - val function: IrFunctionExpression, +data class PropertyMappingTransformTranformation private constructor( + val function: IrExpression, + override val type: IrType, ) : PropertyMappingTransformation { - override val type = function.function.returnType + constructor(functionReference: IrFunctionReference) : this(functionReference, functionReference.symbol.owner.returnType) + constructor(functionExpression: IrFunctionExpression) : this(functionExpression, functionExpression.function.returnType) } data class PropertyMappingViaMapperTransformation( diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt new file mode 100644 index 0000000..88c8c1a --- /dev/null +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt @@ -0,0 +1,75 @@ +package tech.mappie.testing.objects + +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.compile +import tech.mappie.testing.loadObjectMappieClass +import java.io.File + +class MethodReferenceTest { + + data class Input(val x: Int) + data class Output(val value: String) + + @TempDir + lateinit var directory: File + + @Test + fun `map property fromExpression should succeed with method reference`() { + compile(directory) { + file("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.objects.MethodReferenceTest.* + + class Mapper : ObjectMappie() { + override fun map(from: Int) = mapping { + Output::value fromExpression Int::toString + } + } + """ + ) + } satisfies { + isOk() + hasNoMessages() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(101)).isEqualTo(Output("101")) + } + } + + @Test + fun `map property fromProperty should succeed with method reference transform`() { + compile(directory) { + file("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.objects.MethodReferenceTest.* + + class Mapper : ObjectMappie() { + override fun map(from: Input) = mapping { + Output::value fromProperty from::x transform Int::toString + } + } + """ + ) + } satisfies { + isOk() + hasNoMessages() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(Input(101))).isEqualTo(Output("101")) + } + } +} \ No newline at end of file From d3fd913a417fd29e04368f346e705a12ec4e93c7 Mon Sep 17 00:00:00 2001 From: Abel Pelser Date: Thu, 21 Nov 2024 20:43:43 +0100 Subject: [PATCH 2/4] changelog --- website/src/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/src/changelog.md b/website/src/changelog.md index 509950b..950eaf2 100644 --- a/website/src/changelog.md +++ b/website/src/changelog.md @@ -2,6 +2,10 @@ title: "Changelog" layout: "layouts/changelog.html" changelog: + - date: "tbd" + title: "v0.10.0" + items: + - "Added support for method references in fromExpression and transform" - date: "2024-11-18" title: "v0.9.2" items: From 5e34d2678edd92021a5ab3b084ab86b12b3efd56 Mon Sep 17 00:00:00 2001 From: Abel Pelser Date: Thu, 21 Nov 2024 22:49:06 +0100 Subject: [PATCH 3/4] feedback PR --- .../classes/ExplicitClassMappingCollector.kt | 4 +- .../testing/objects/FromExpressionTest.kt | 29 +++++++ .../testing/objects/MethodReferenceTest.kt | 75 ------------------- .../objects/ObjectWithDifferentValuesTest.kt | 30 ++++++++ 4 files changed, 61 insertions(+), 77 deletions(-) delete mode 100644 compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt diff --git a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt index 6668e77..24863de 100644 --- a/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt +++ b/compiler-plugin/src/main/kotlin/tech/mappie/resolving/classes/ExplicitClassMappingCollector.kt @@ -54,7 +54,7 @@ private class ClassMappingStatementCollector(private val context: ResolverContex } IDENTIFIER_FROM_EXPRESSION -> { val target = expression.extensionReceiver!!.accept(TargetNameCollector(context), data) - target to ExpressionMappingSource(expression.valueArguments.first() as IrExpression) + target to ExpressionMappingSource(expression.valueArguments.first()!!) } IDENTIFIER_VIA -> { expression.dispatchReceiver!!.accept(data).let { (name, source) -> @@ -70,7 +70,7 @@ private class ClassMappingStatementCollector(private val context: ResolverContex when (it) { is IrFunctionExpression -> PropertyMappingTransformTranformation(it) is IrFunctionReference -> PropertyMappingTransformTranformation(it) - else -> throw MappiePanicException("Unexpected expression type: ${expression.type}") + else -> throw MappiePanicException("Unexpected expression type: ${expression.dumpKotlinLike()}") } } ) diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt index ec95d77..dcf7c52 100644 --- a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt @@ -42,4 +42,33 @@ class FromExpressionTest { assertThat(mapper.map(Unit)).isEqualTo(Output(Unit::class.simpleName!!)) } } + + @Test + fun `map property fromExpression should succeed with method reference`() { + compile(directory) { + file("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.objects.FromExpressionTest.* + + class Mapper : ObjectMappie() { + override fun map(from: Int) = mapping { + Output::value fromExpression Int::toString + } + } + """ + ) + } satisfies { + isOk() + hasNoMessages() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(101)).isEqualTo(Output("101")) + } + } } \ No newline at end of file diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt deleted file mode 100644 index 88c8c1a..0000000 --- a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/MethodReferenceTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package tech.mappie.testing.objects - -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.compile -import tech.mappie.testing.loadObjectMappieClass -import java.io.File - -class MethodReferenceTest { - - data class Input(val x: Int) - data class Output(val value: String) - - @TempDir - lateinit var directory: File - - @Test - fun `map property fromExpression should succeed with method reference`() { - compile(directory) { - file("Test.kt", - """ - import tech.mappie.api.ObjectMappie - import tech.mappie.testing.objects.MethodReferenceTest.* - - class Mapper : ObjectMappie() { - override fun map(from: Int) = mapping { - Output::value fromExpression Int::toString - } - } - """ - ) - } satisfies { - isOk() - hasNoMessages() - - val mapper = classLoader - .loadObjectMappieClass("Mapper") - .constructors - .first() - .call() - - assertThat(mapper.map(101)).isEqualTo(Output("101")) - } - } - - @Test - fun `map property fromProperty should succeed with method reference transform`() { - compile(directory) { - file("Test.kt", - """ - import tech.mappie.api.ObjectMappie - import tech.mappie.testing.objects.MethodReferenceTest.* - - class Mapper : ObjectMappie() { - override fun map(from: Input) = mapping { - Output::value fromProperty from::x transform Int::toString - } - } - """ - ) - } satisfies { - isOk() - hasNoMessages() - - val mapper = classLoader - .loadObjectMappieClass("Mapper") - .constructors - .first() - .call() - - assertThat(mapper.map(Input(101))).isEqualTo(Output("101")) - } - } -} \ No newline at end of file diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt index 61403c4..7a96c38 100644 --- a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt @@ -118,4 +118,34 @@ class ObjectWithDifferentValuesTest { assertThat(mapper.map(Input("Sjon", 58))).isEqualTo(Output("Sjon", 58)) } } + + @Test + fun `map property fromProperty should succeed with method reference transform`() { + compile(directory) { + file("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.objects.ObjectWithDifferentValuesTest.* + + class Mapper : ObjectMappie() { + override fun map(from: Input) = mapping { + Output::age fromProperty from::firstname transform String::toInt + Output::name fromProperty from::age transform Int::toString + } + } + """ + ) + } satisfies { + isOk() + hasNoMessages() + + val mapper = classLoader + .loadObjectMappieClass("Mapper") + .constructors + .first() + .call() + + assertThat(mapper.map(Input("101", 9))).isEqualTo(Output("9", 101)) + } + } } \ No newline at end of file From 8c73a2f257e2b129e45cc9881a844645cf0a4a5e Mon Sep 17 00:00:00 2001 From: Abel Pelser Date: Fri, 22 Nov 2024 00:11:27 +0100 Subject: [PATCH 4/4] feedback PR - tests --- .../testing/objects/FromExpressionTest.kt | 21 ++++++++++++++++ .../objects/ObjectWithDifferentValuesTest.kt | 24 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt index dcf7c52..34ae630 100644 --- a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/FromExpressionTest.kt @@ -71,4 +71,25 @@ class FromExpressionTest { assertThat(mapper.map(101)).isEqualTo(Output("101")) } } + + @Test + fun `map property fromExpression should fail with method reference with wrong return type`() { + compile(directory) { + file("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.objects.FromExpressionTest.* + + class Mapper : ObjectMappie() { + override fun map(from: Int) = mapping { + Output::value fromExpression Int::toInt + } + } + """ + ) + } satisfies { + isCompilationError() + hasErrorMessage(6, "Target Output::value of type String cannot be assigned from expression of type Int") + } + } } \ No newline at end of file diff --git a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt index 7a96c38..85f110a 100644 --- a/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt +++ b/compiler-plugin/src/test/kotlin/tech/mappie/testing/objects/ObjectWithDifferentValuesTest.kt @@ -6,6 +6,7 @@ import org.junit.jupiter.api.io.TempDir import tech.mappie.testing.compilation.compile import tech.mappie.testing.loadObjectMappieClass import java.io.File +import kotlin.random.Random class ObjectWithDifferentValuesTest { @@ -148,4 +149,27 @@ class ObjectWithDifferentValuesTest { assertThat(mapper.map(Input("101", 9))).isEqualTo(Output("9", 101)) } } + + @Test + fun `map property fromProperty should fail with method reference with wrong signature`() { + compile(directory) { + file("Test.kt", + """ + import tech.mappie.api.ObjectMappie + import tech.mappie.testing.objects.ObjectWithDifferentValuesTest.* + + class Mapper : ObjectMappie() { + override fun map(from: Input) = mapping { + Output::age fromProperty from::firstname transform String::toString + Output::name fromProperty from::age transform Int::toInt + } + } + """ + ) + } satisfies { + isCompilationError() + hasErrorMessage(6, "Inapplicable candidate(s): fun toString(): String") + hasErrorMessage(7, "Inapplicable candidate(s): fun toInt(): Int") + } + } } \ No newline at end of file