Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and added support for generating object mappers #96

Merged
merged 22 commits into from
Sep 22, 2024
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
16 changes: 0 additions & 16 deletions compiler-plugin/src/main/kotlin/tech/mappie/BaseVisitor.kt

This file was deleted.

75 changes: 39 additions & 36 deletions compiler-plugin/src/main/kotlin/tech/mappie/MappieIrRegistrar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,62 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.fileEntry
import tech.mappie.generation.*
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import tech.mappie.exceptions.MappiePanicException
import tech.mappie.generation.CodeGenerationContext
import tech.mappie.generation.CodeGenerationModelFactory
import tech.mappie.generation.MappieCodeGenerator
import tech.mappie.preprocessing.DefinitionsCollector
import tech.mappie.resolving.*
import tech.mappie.util.*
import tech.mappie.selection.MappingSelector
import tech.mappie.util.isMappieMapFunction
import tech.mappie.util.location
import tech.mappie.validation.MappingValidation
import tech.mappie.validation.Problem
import tech.mappie.validation.ValidationContext

class MappieIrRegistrar(
private val messageCollector: MessageCollector,
private val configuration: MappieConfiguration,
) : IrGenerationExtension {

override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
context = MappiePluginContext(messageCollector, configuration, pluginContext)
context = pluginContext

val symbols = MappieDefinitionsCollector().collect(moduleFragment)
val mappings = moduleFragment.accept(MappingResolver(), symbols)
handleMappiePanic {
val context = DefinitionsCollector(createMappieContext(pluginContext)).collect(moduleFragment)
val requests = moduleFragment.accept(MappingRequestResolver(), context)

val validated = mappings.mapValues {
it.value.map { mapping -> mapping to MappingValidation.of(it.key.fileEntry, mapping) }
}

val valids = validated.mapValues {
it.value.filter { it.second.isValid() }
}
if (valids.all { it.value.isNotEmpty() }) {
val selected = valids.mapValues {
MappingSelector.of(it.value).select()!!
}
requests.forEach { (clazz, options) ->
val selected = MappingSelector.of(options.associateWith {
MappingValidation.of(ValidationContext(context, context.definitions, emptyList(), it.origin), it)
}).select()

selected.forEach { function, (_, validation) ->
validation.warnings().forEach { warning ->
log(warning, warning.location ?: location(function))
}
}
selected?.let { (solution, validation) ->
val function = clazz.declarations
.filterIsInstance<IrSimpleFunction>()
.first { it.isMappieMapFunction() }

val generation = MappieGeneration(
mappings = selected.mapValues { it.value.first },
generated = selected.flatMap { (it.value.first as? ConstructorCallMapping)?.generated ?: emptySet() }.toSet()
)
moduleFragment.accept(MappieIrGenerator(generation), null)
} else {
val invalids = validated.filter { it.value.none { it.second.isValid() } }
invalids.forEach { (function, mappings) ->
if (mappings.isEmpty()) {
logError("No constructor visible to use", location(function))
} else {
logAll(mappings.first().second.problems, location(function))
}
context.logger.logAll(validation.problems, location(function))
if (solution != null) {
val model = CodeGenerationModelFactory.of(solution).construct(function)
clazz.accept(MappieCodeGenerator(CodeGenerationContext(context, model, context.definitions, emptyMap())), null)
}
} ?: context.logger.log(Problem.error("Target class has no accessible constructor", location(clazz)))
}
}
}

private fun handleMappiePanic(function: () -> Unit): Unit =
runCatching { function() }.getOrElse { if (it is MappiePanicException) Unit else throw it }

private fun createMappieContext(pluginContext: IrPluginContext) = object : MappieContext {
override val pluginContext = pluginContext
override val configuration = [email protected]
override val logger = MappieLogger(configuration.warningsAsErrors, messageCollector)
}

companion object {
lateinit var context: MappiePluginContext
lateinit var context: IrPluginContext
}
}
46 changes: 46 additions & 0 deletions compiler-plugin/src/main/kotlin/tech/mappie/MappieLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package tech.mappie

import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import tech.mappie.validation.Problem

class MappieLogger(val warningsAsErrors: Boolean, messageCollector: MessageCollector)
: MessageCollector by messageCollector {

fun logAll(problems: List<Problem>, location: CompilerMessageSourceLocation? = null) =
problems.forEach { log(it, location) }

fun log(problem: Problem, location: CompilerMessageSourceLocation? = null) =
when (problem.severity) {
Problem.Severity.ERROR -> logError(messageOf(problem), problem.location ?: location)
Problem.Severity.WARNING -> logWarn(messageOf(problem), problem.location ?: location)
}

fun logInfo(message: String, location: CompilerMessageSourceLocation? = null) =
info(message, location)

fun logWarn(message: String, location: CompilerMessageSourceLocation? = null) =
warn(message, location)

fun logError(message: String, location: CompilerMessageSourceLocation? = null) =
error(message, location)

fun info(message: String, location: CompilerMessageSourceLocation? = null) =
report(CompilerMessageSeverity.INFO, message, location)

fun warn(message: String, location: CompilerMessageSourceLocation? = null) =
if (warningsAsErrors) {
report(CompilerMessageSeverity.ERROR, message, location)
} else {
report(CompilerMessageSeverity.WARNING, message, location)
}

fun error(message: String, location: CompilerMessageSourceLocation? = null) =
report(CompilerMessageSeverity.ERROR, message, location)

private fun messageOf(problem: Problem) =
problem.description + System.lineSeparator() + problem.suggestions
.mapIndexed { i, it -> i + 1 to it }
.joinToString(separator = "") { " ${it.first}. ${it.second}" + System.lineSeparator() }
}
36 changes: 18 additions & 18 deletions compiler-plugin/src/main/kotlin/tech/mappie/MappiePluginContext.kt
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package tech.mappie

import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
import org.jetbrains.kotlin.ir.builders.IrGeneratorContextBase
import org.jetbrains.kotlin.ir.builders.Scope
import tech.mappie.util.logError
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import tech.mappie.api.EnumMappie
import tech.mappie.api.ObjectMappie

class MappiePluginContext(
val messageCollector: MessageCollector,
val configuration: MappieConfiguration,
irPluginContext: IrPluginContext,
): IrPluginContext by irPluginContext {
interface MappieContext {
val pluginContext: IrPluginContext
val logger: MappieLogger
val configuration: MappieConfiguration
}

fun blockBody(scope: Scope, body: IrBlockBodyBuilder.() -> Unit) =
IrBlockBodyBuilder(IrGeneratorContextBase(irBuiltIns), scope, scope.scopeOwnerSymbol.owner.startOffset, scope.scopeOwnerSymbol.owner.endOffset)
.blockBody(body)
fun MappieContext.referenceObjectMappieClass(): IrClassSymbol =
pluginContext.referenceClass(ClassId(FqName("tech.mappie.api"), Name.identifier(ObjectMappie::class.simpleName!!)))!!

}
fun MappieContext.referenceEnumMappieClass(): IrClassSymbol =
pluginContext.referenceClass(ClassId(FqName("tech.mappie.api"), Name.identifier(EnumMappie::class.simpleName!!)))!!

internal fun mappieTerminate(description: String, location: CompilerMessageLocation?): Nothing {
logError(description, location)
error("Mappie failed due to $description")
}
fun MappieContext.referenceFunctionLet() =
pluginContext.referenceFunctions(CallableId(FqName("kotlin"), Name.identifier("let"))).first()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package tech.mappie.exceptions

import org.jetbrains.kotlin.ir.IrElement

class MappiePanicException(message: String, val origin: IrElement? = null) : Exception(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tech.mappie.generation

import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.types.IrType
import tech.mappie.MappieContext
import tech.mappie.resolving.MappieDefinition

class CodeGenerationContext(
context: MappieContext,
val model: CodeGenerationModel,
val definitions: List<MappieDefinition>,
val generated: Map<Pair<IrType, IrType>, IrClass>,
) : MappieContext by context {

fun copy(
model: CodeGenerationModel = this.model,
definitions: List<MappieDefinition> = this.definitions,
generated: Map<Pair<IrType, IrType>, IrClass> = this.generated
) = CodeGenerationContext(this, model, definitions, generated)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tech.mappie.generation

import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrEnumEntry
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.types.IrType
import tech.mappie.resolving.classes.sources.ClassMappingSource
import tech.mappie.resolving.classes.targets.ClassMappingTarget
import tech.mappie.resolving.enums.EnumMappingTarget

sealed interface CodeGenerationModel {
val declaration: IrFunction
}

data class EnumMappieCodeGenerationModel(
override val declaration: IrFunction,
val source: IrType,
val target: IrType,
val mappings: Map<IrEnumEntry, EnumMappingTarget>,
) : CodeGenerationModel

data class ClassMappieCodeGenerationModel(
override val declaration: IrFunction,
val constructor: IrConstructor,
val mappings: Map<ClassMappingTarget, ClassMappingSource>,
) : CodeGenerationModel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package tech.mappie.generation

import org.jetbrains.kotlin.ir.declarations.IrFunction
import tech.mappie.generation.classes.ClassMappieCodeGenerationModelFactory
import tech.mappie.generation.enums.EnumMappieCodeGenerationModelFactory
import tech.mappie.resolving.ClassMappingRequest
import tech.mappie.resolving.EnumMappingRequest
import tech.mappie.resolving.MappingRequest

interface CodeGenerationModelFactory {

fun construct(function: IrFunction): CodeGenerationModel

companion object {
fun of(request: MappingRequest): CodeGenerationModelFactory =
when (request) {
is ClassMappingRequest -> ClassMappieCodeGenerationModelFactory(request)
is EnumMappingRequest -> EnumMappieCodeGenerationModelFactory(request)
}
}
}
Loading