diff --git a/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ClassResolver.kt b/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ClassResolver.kt index e50eb915..cd39d045 100644 --- a/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ClassResolver.kt +++ b/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ClassResolver.kt @@ -1,11 +1,9 @@ package io.github.mappie.resolving.classes import io.github.mappie.resolving.* -import io.github.mappie.util.getterName import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.util.fileEntry -import org.jetbrains.kotlin.ir.util.hasDefaultValue import org.jetbrains.kotlin.ir.util.isClass class ClassResolver(private val declaration: IrFunction) { @@ -16,39 +14,16 @@ class ClassResolver(private val declaration: IrFunction) { require(declaration.returnType.getClass()!!.isClass) } - fun resolve(): List { - val possibilities = declaration.accept(ConstructorsCollector(), Unit) - val getters = sourceParameter.accept(GettersCollector(), Unit) - val dispatchReceiverSymbol = sourceParameter.symbol - val concreteSources = declaration.body?.accept(ObjectBodyCollector(declaration.fileEntry, dispatchReceiverSymbol), Unit) ?: emptyList() + fun resolve(): List { + val constructor = ObjectMappingsConstructor.of(declaration.returnType, sourceParameter) + .apply { getters.addAll(sourceParameter.accept(GettersCollector(), Unit)) } - return possibilities.map { constructor -> - val targets = constructor.valueParameters - val mappings = targets.associateWith { target -> - val concreteSource = concreteSources.firstOrNull { it.first == target.name } + declaration.body?.accept(ObjectMappingBodyCollector(declaration.fileEntry, sourceParameter.symbol), constructor) - if (concreteSource != null) { - listOf(concreteSource.second) - } else { - val getter = getters.firstOrNull { getter -> - getter.name == getterName(target.name) - } - if (getter != null) { - listOf(PropertySource(getter.symbol, target.type, sourceParameter.symbol, true)) - } else if (target.hasDefaultValue()) { - listOf(DefaultParameterValueSource(target.defaultValue!!.expression)) - } else { - emptyList() - } - } - } - - ConstructorCallMapping( - targetType = declaration.returnType, - sourceType = sourceParameter.type, - symbol = constructor.symbol, - mappings = mappings - ) + return declaration.accept(ConstructorsCollector(), Unit).map { + ObjectMappingsConstructor.of(constructor).apply { + this.constructor = it + }.construct() } } } \ No newline at end of file diff --git a/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/SourcesCollector.kt b/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ObjectMappingBodyCollector.kt similarity index 90% rename from compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/SourcesCollector.kt rename to compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ObjectMappingBodyCollector.kt index e7f48727..7f30622a 100644 --- a/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/SourcesCollector.kt +++ b/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ObjectMappingBodyCollector.kt @@ -23,34 +23,34 @@ import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name -class ObjectBodyCollector( +class ObjectMappingBodyCollector( file: IrFileEntry, private val dispatchReceiverSymbol: IrValueSymbol, -) : BaseVisitor>, Unit>(file) { +) : BaseVisitor(file) { - override fun visitBlockBody(body: IrBlockBody, data: Unit): List> { + override fun visitBlockBody(body: IrBlockBody, data: ObjectMappingsConstructor): ObjectMappingsConstructor { return body.statements.single().accept(data) } - override fun visitReturn(expression: IrReturn, data: Unit): List> { + override fun visitReturn(expression: IrReturn, data: ObjectMappingsConstructor): ObjectMappingsConstructor { return expression.value.accept(data) } - override fun visitCall(expression: IrCall, data: Unit): List> { + override fun visitCall(expression: IrCall, data: ObjectMappingsConstructor): ObjectMappingsConstructor { return when (expression.symbol.owner.name) { IDENTIFIER_MAPPING -> { - expression.valueArguments.first()?.accept(data) ?: emptyList() + expression.valueArguments.first()?.accept(data) ?: data } else -> { - emptyList() + data } } } - override fun visitFunctionExpression(expression: IrFunctionExpression, data: Unit): List> { - return expression.function.body!!.statements.mapNotNull { - it.accept(ObjectBodyStatementCollector(file, dispatchReceiverSymbol), Unit) - } + override fun visitFunctionExpression(expression: IrFunctionExpression, data: ObjectMappingsConstructor): ObjectMappingsConstructor { + return expression.function.body?.statements?.fold(data) { acc, current -> + acc.let { current.accept(ObjectBodyStatementCollector(file, dispatchReceiverSymbol), Unit)?.let { acc.explicit(it) } ?: it } + } ?: data } } @@ -62,13 +62,13 @@ private class ObjectBodyStatementCollector( override fun visitCall(expression: IrCall, data: Unit): Pair? { return when (expression.symbol.owner.name) { IDENTIFIER_MAPPED_FROM_PROPERTY, IDENTIFIER_MAPPED_FROM_CONSTANT -> { - val target = expression.extensionReceiver!!.accept(TargetValueCollector(), Unit) + val target = expression.extensionReceiver!!.accept(TargetValueCollector(), data) val source = expression.valueArguments.first()!!.accept(SourceValueCollector(dispatchReceiverSymbol), Unit) target to source } IDENTIFIER_MAPPED_FROM_EXPRESSION -> { - val target = expression.extensionReceiver!!.accept(TargetValueCollector(), Unit) + val target = expression.extensionReceiver!!.accept(TargetValueCollector(), data) val source = expression.valueArguments.first() as IrFunctionExpression target to ExpressionSource( @@ -131,8 +131,6 @@ private class MapperReferenceCollector : BaseVisitor .wrap(expression) } - - override fun visitCall(expression: IrCall, data: Unit): IrFunctionExpression { require(expression.origin == IrStatementOrigin.GET_PROPERTY) diff --git a/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ObjectMappingsConstructor.kt b/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ObjectMappingsConstructor.kt new file mode 100644 index 00000000..c87363e1 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/io/github/mappie/resolving/classes/ObjectMappingsConstructor.kt @@ -0,0 +1,65 @@ +package io.github.mappie.resolving.classes + +import io.github.mappie.resolving.ConstructorCallMapping +import io.github.mappie.util.getterName +import org.jetbrains.kotlin.ir.declarations.IrConstructor +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.IrValueParameter +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.util.hasDefaultValue +import org.jetbrains.kotlin.name.Name + +class ObjectMappingsConstructor(val targetType: IrType, val source: IrValueParameter) { + + var getters = mutableListOf() + + // TODO: Map of lists for detecting duplicates + var explicit = mutableListOf>() + + var constructor: IrConstructor? = null + + private val targets + get() = constructor?.valueParameters ?: emptyList() + + fun construct(): ConstructorCallMapping { + val mappings = targets.associateWith { target -> + val concreteSource = explicit.firstOrNull { it.first == target.name } + + if (concreteSource != null) { + listOf(concreteSource.second) + } else { + val getter = getters.firstOrNull { getter -> + getter.name == getterName(target.name) + } + if (getter != null) { + listOf(PropertySource(getter.symbol, target.type, source.symbol, true)) + } else if (target.hasDefaultValue()) { + listOf(DefaultParameterValueSource(target.defaultValue!!.expression)) + } else { + emptyList() + } + } + } + + return ConstructorCallMapping( + targetType = targetType, + sourceType = source.type, + symbol = constructor!!.symbol, + mappings = mappings + ) + } + + fun explicit(entry: Pair): ObjectMappingsConstructor = + apply { explicit.add(entry) } + + companion object { + fun of(constructor: ObjectMappingsConstructor) = + ObjectMappingsConstructor(constructor.targetType, constructor.source).apply { + getters = constructor.getters + explicit = constructor.explicit + } + + fun of(targetType: IrType, source: IrValueParameter) = + ObjectMappingsConstructor(targetType, source) + } +} \ No newline at end of file