From 49c264b9ad884bdec8ff2f251637519fc9e65373 Mon Sep 17 00:00:00 2001 From: Willem Veelenturf Date: Wed, 22 Apr 2026 12:48:16 +0200 Subject: [PATCH] fix: allow enum auto-mapping when source is a strict subset of target Replace set equality with toEntries.containsAll(fromEntries) in the FIR checker and enumsEqual. The IR side already emits Enum.valueOf(source.name), which resolves correctly whenever every source entry name exists in the target. Target-only entries remain unreachable by construction. Closes #27 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../compiler/fir/KMapperFirMappingChecker.kt | 2 +- .../kmapper/compiler/fir/KmapperFirLogic.kt | 2 +- .../flock/kmapper/EnumMappingTest.kt | 37 +++++++++++++++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KMapperFirMappingChecker.kt b/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KMapperFirMappingChecker.kt index 65efc1b..9861897 100644 --- a/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KMapperFirMappingChecker.kt +++ b/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KMapperFirMappingChecker.kt @@ -70,7 +70,7 @@ class KMapperFirMappingChecker(val collector: MessageCollector, private val sess if (fromEnum != null && toEnum != null) { val fromEntries = fromEnum.enumEntryNames().toSet() val toEntries = toEnum.enumEntryNames().toSet() - if (fromEntries == toEntries) return + if (toEntries.containsAll(fromEntries)) return } } diff --git a/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KmapperFirLogic.kt b/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KmapperFirLogic.kt index db3df19..2b628d5 100644 --- a/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KmapperFirLogic.kt +++ b/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/fir/KmapperFirLogic.kt @@ -101,7 +101,7 @@ private fun enumsEqual(to: Field, from: Field): Boolean { to.type.toRegularClassSymbol(session)?.takeIf { it.isEnumClass }?.enumEntryNames()?.toSet() ?: return false val fromEntries = from.type.toRegularClassSymbol(session)?.takeIf { it.isEnumClass }?.enumEntryNames()?.toSet() ?: return false - return toEntries == fromEntries + return toEntries.containsAll(fromEntries) } context(session: FirSession, collector: MessageCollector) diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt index ab49b88..ba387c3 100644 --- a/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt +++ b/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt @@ -43,7 +43,7 @@ class EnumMappingTest { } @Test - fun shouldFail_notEqualEnums() { + fun shouldCompile_sourceSubsetOfTarget() { IntegrationTest(options) .file("App.kt") { $$""" @@ -51,11 +51,42 @@ class EnumMappingTest { | |import community.flock.kmapper.mapper | - |enum class Gender { MALE, FEMALE } + |enum class Generation { GEN_Z, BOOMER } + |data class Person(val name: String, val age: Int, val generation: Generation) + | + |enum class GenerationDto { GEN_Z, MILLENNIALS, BOOMER } + |data class PersonDto(val name: String, val age: Int, val generation: GenerationDto) + | + |fun main() { + | val person = Person("John", 30, Generation.GEN_Z) + | val dto: PersonDto = person.mapper() + | println(dto) + |} + | + """.trimMargin() + } + .compileSuccess { output -> + assertTrue( + output.contains("PersonDto(name=John, age=30, generation=GEN_Z)"), + "Expected PersonDto(name=John, age=30, generation=GEN_Z) in output" + ) + } + } + + @Test + fun shouldFail_sourceSupersetOfTarget() { + IntegrationTest(options) + .file("App.kt") { + $$""" + |package sample + | + |import community.flock.kmapper.mapper + | + |enum class Gender { MALE, FEMALE, OTHER } |data class Address(val street: String, val city: String) |data class Person(val name: String, val gender: Gender, val address: Address) | - |enum class GenderDto { FEMALE, MALE, X } + |enum class GenderDto { FEMALE, MALE } |data class AddressDto(val street: String, val city: String) |data class PersonDto(val name: String, val gender: GenderDto, val address: AddressDto) |