Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import community.flock.kmapper.compiler.fir.Field
import community.flock.kmapper.compiler.fir.deepEqual
import community.flock.kmapper.compiler.fir.enumEntryNames
import community.flock.kmapper.compiler.util.Diagnostics
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
Expand All @@ -15,6 +16,7 @@ import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtensi
import org.jetbrains.kotlin.fir.declarations.constructors
import org.jetbrains.kotlin.fir.declarations.declaredProperties
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression
import org.jetbrains.kotlin.fir.expressions.FirCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
Expand All @@ -30,8 +32,11 @@ import org.jetbrains.kotlin.fir.types.FirTypeProjectionWithVariance
import org.jetbrains.kotlin.fir.types.coneTypeOrNull
import org.jetbrains.kotlin.fir.types.resolvedType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.name.FqName

@OptIn(DirectDeclarationsAccess::class, SymbolInternals::class)
class KMapperFirMappingChecker(val collector: MessageCollector, private val session: FirSession) :

FirCallChecker(MppCheckerKind.Common) {
Expand All @@ -53,6 +58,21 @@ class KMapperFirMappingChecker(val collector: MessageCollector, private val sess

val function = expression as? FirFunctionCall ?: return

// Skip field resolution for direct enum-to-enum mapping with matching entry names
val fromType = function.typeArguments.getOrNull(1)
?.let { it as? FirTypeProjectionWithVariance }?.typeRef?.coneTypeOrNull
val toType = function.typeArguments.firstOrNull()
?.let { it as? FirTypeProjectionWithVariance }?.typeRef?.coneTypeOrNull
if (fromType != null && toType != null) {
val fromEnum = fromType.toRegularClassSymbol(session)?.takeIf { it.isEnumClass }
val toEnum = toType.toRegularClassSymbol(session)?.takeIf { it.isEnumClass }
if (fromEnum != null && toEnum != null) {
val fromEntries = fromEnum.enumEntryNames().toSet()
val toEntries = toEnum.enumEntryNames().toSet()
if (fromEntries == toEntries) return
}
}

val fromFields = function.typeArguments
.getOrNull(1)
?.let { it as? FirTypeProjectionWithVariance }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private fun ConeKotlinType.resolvePropertyFields(): List<Field> {
}

@OptIn(DirectDeclarationsAccess::class, SymbolInternals::class)
private fun FirRegularClassSymbol.enumEntryNames(): List<Name> =
internal fun FirRegularClassSymbol.enumEntryNames(): List<Name> =
this.fir.declarations
.filterIsInstance<FirEnumEntry>()
.map { it.name }
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ class KMapperIrBuildMapperVisitor(
}

val toShape = toTypeArgument.convertShape()
val fromShape = fromTypeArgument.convertShape()

// Direct enum-to-enum mapping: use valueOf(name)
if (toShape.isEnum() && fromShape.isEnum()) {
return builder.construct(receiverArgument, toShape, fromShape)
}

val constructorCall = builder.irCallConstructor(toShape.constructor.symbol, emptyList()).apply {
toShape.fields.onEachIndexed { index, field ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,64 @@ class EnumMappingTest {
}
}

@Test
fun shouldCompile_directEnumWithLabelProperty() {
IntegrationTest(options)
.file("App.kt") {
$$"""
|package sample
|
|import community.flock.kmapper.mapper
|
|enum class MimeType { IMAGE_JPEG, IMAGE_PNG }
|enum class MimeTypeDto(val label: String) { IMAGE_JPEG("IMAGE_JPEG"), IMAGE_PNG("IMAGE_PNG") }
|
|fun MimeType.toDto(): MimeTypeDto = mapper {}
|
|fun main() {
| val dto = MimeType.IMAGE_JPEG.toDto()
| println(dto)
|}
|
""".trimMargin()
}
.compileSuccess { output ->
assertTrue(
output.contains("IMAGE_JPEG"),
"Expected IMAGE_JPEG in output"
)
}
}

@Test
fun shouldCompile_directEnumWithLabelPropertyReverse() {
IntegrationTest(options)
.file("App.kt") {
$$"""
|package sample
|
|import community.flock.kmapper.mapper
|
|enum class MimeTypeDto(val label: String) { IMAGE_JPEG("IMAGE_JPEG"), IMAGE_PNG("IMAGE_PNG") }
|enum class MimeType { IMAGE_JPEG, IMAGE_PNG }
|
|fun MimeTypeDto.toMimeType(): MimeType = mapper {}
|
|fun main() {
| val result = MimeTypeDto.IMAGE_JPEG.toMimeType()
| println(result)
|}
|
""".trimMargin()
}
.compileSuccess { output ->
assertTrue(
output.contains("IMAGE_JPEG"),
"Expected IMAGE_JPEG in output"
)
}
}

@Test
fun shouldSuccess_nestedEnumMapping() {
IntegrationTest(options)
Expand Down
Loading