```Caused by: java.lang.AssertionError: Unexpected...
# compiler
m
Copy code
Caused by: java.lang.AssertionError: Unexpected IR element found during code generation. Either code generation for it is not implemented, or it should have been lowered:
FUN name:<anonymous> visibility:public modality:FINAL <> (it:kotlin.Any) returnType:kotlin.Any
How do I fix this? I'm trying to build a lambda, and in the parent declaration (see thread) the generated IR largely seems fine to me.
IR:
Copy code
CONSTRUCTOR GENERATED[com.martmists.serialization.CodecFIRGenerator.GeneratedForCodec] visibility:private returnType:com.martmists.serialization.TestData.Companion [primary]
  BLOCK_BODY
    DELEGATING_CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in kotlin.Any'
    BLOCK type=kotlin.Unit origin=null
      SET_FIELD 'FIELD name:CODEC type:kotlin.Any visibility:public declared in com.martmists.serialization.TestData.Companion' type=kotlin.Unit origin=INITIALIZE_FIELD
        receiver: GET_VAR '<this>: com.martmists.serialization.TestData.Companion declared in com.martmists.serialization.TestData.Companion' type=com.martmists.serialization.TestData.Companion origin=null
        value: CALL 'public open fun create <O> (p0: @[FlexibleNullability] java.util.function.Function<@[FlexibleNullability] com.mojang.serialization.codecs.RecordCodecBuilder.Instance<@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.create?>?, out @[FlexibleNullability] <http://com.mojang.datafixers.kinds.App|com.mojang.datafixers.kinds.App><@[FlexibleNullability] <http://com.mojang.serialization.codecs.RecordCodecBuilder.Mu|com.mojang.serialization.codecs.RecordCodecBuilder.Mu><@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.create?>?, @[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.create?>?>?): @[FlexibleNullability] com.mojang.serialization.Codec<@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.create?>? declared in com.mojang.serialization.codecs.RecordCodecBuilder' type=@[FlexibleNullability] com.mojang.serialization.Codec<@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.create?>? origin=null
          TYPE_ARG O: com.martmists.serialization.TestData
          ARG p0: BLOCK type=kotlin.Any origin=LAMBDA
            FUN name:<anonymous> visibility:public modality:FINAL returnType:kotlin.Any
              VALUE_PARAMETER kind:Regular name:it index:0 type:kotlin.Any
              EXPRESSION_BODY
                CALL 'public open fun apply <R> (p0: @[FlexibleNullability] com.mojang.datafixers.kinds.Applicative<@[FlexibleNullability] F of com.mojang.datafixers.Products.P2?, *>?, p1: @[FlexibleNullability] java.util.function.BiFunction<@[FlexibleNullability] T1 of com.mojang.datafixers.Products.P2?, @[FlexibleNullability] T2 of com.mojang.datafixers.Products.P2?, @[FlexibleNullability] R of com.mojang.datafixers.Products.P2.apply?>?): @[FlexibleNullability] <http://com.mojang.datafixers.kinds.App|com.mojang.datafixers.kinds.App><@[FlexibleNullability] F of com.mojang.datafixers.Products.P2?, @[FlexibleNullability] R of com.mojang.datafixers.Products.P2.apply?>? declared in com.mojang.datafixers.Products.P2' type=@[FlexibleNullability] <http://com.mojang.datafixers.kinds.App|com.mojang.datafixers.kinds.App><@[FlexibleNullability] F of com.mojang.datafixers.Products.P2?, @[FlexibleNullability] R of com.mojang.datafixers.Products.P2.apply?>? origin=null
                  TYPE_ARG R: com.martmists.serialization.TestData
                  ARG <this>: CALL 'public open fun group <T1, T2> (p0: @[FlexibleNullability] <http://com.mojang.datafixers.kinds.App|com.mojang.datafixers.kinds.App><@[FlexibleNullability] <http://com.mojang.serialization.codecs.RecordCodecBuilder.Mu|com.mojang.serialization.codecs.RecordCodecBuilder.Mu><@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.Instance?>?, @[FlexibleNullability] T1 of com.mojang.serialization.codecs.RecordCodecBuilder.Instance.group?>?, p1: @[FlexibleNullability] <http://com.mojang.datafixers.kinds.App|com.mojang.datafixers.kinds.App><@[FlexibleNullability] <http://com.mojang.serialization.codecs.RecordCodecBuilder.Mu|com.mojang.serialization.codecs.RecordCodecBuilder.Mu><@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.Instance?>?, @[FlexibleNullability] T2 of com.mojang.serialization.codecs.RecordCodecBuilder.Instance.group?>?): @[FlexibleNullability] com.mojang.datafixers.Products.P2<@[FlexibleNullability] <http://com.mojang.serialization.codecs.RecordCodecBuilder.Mu|com.mojang.serialization.codecs.RecordCodecBuilder.Mu><@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.Instance?>?, @[FlexibleNullability] T1 of com.mojang.serialization.codecs.RecordCodecBuilder.Instance.group?, @[FlexibleNullability] T2 of com.mojang.serialization.codecs.RecordCodecBuilder.Instance.group?>? [fake_override] declared in com.mojang.serialization.codecs.RecordCodecBuilder.Instance' type=@[FlexibleNullability] com.mojang.datafixers.Products.P2<@[FlexibleNullability] <http://com.mojang.serialization.codecs.RecordCodecBuilder.Mu|com.mojang.serialization.codecs.RecordCodecBuilder.Mu><@[FlexibleNullability] O of com.mojang.serialization.codecs.RecordCodecBuilder.Instance?>?, @[FlexibleNullability] T1 of com.mojang.serialization.codecs.RecordCodecBuilder.Instance.group?, @[FlexibleNullability] T2 of com.mojang.serialization.codecs.RecordCodecBuilder.Instance.group?>? origin=null
                    TYPE_ARG T1: <http://kotlin.Int|kotlin.Int>
                    TYPE_ARG T2: kotlin.String
                    ARG <this>: GET_VAR 'it: kotlin.Any declared in com.martmists.serialization.TestData.Companion.<init>.<anonymous>' type=kotlin.Any origin=null
                    ARG p0: CALL 'public final fun forGetter <O> (p0: @[FlexibleNullability] java.util.function.Function<@[FlexibleNullability] O of com.mojang.serialization.MapCodec.forGetter?, @[FlexibleNullability] A of com.mojang.serialization.MapCodec?>?): @[FlexibleNullability] com.mojang.serialization.codecs.RecordCodecBuilder<@[FlexibleNullability] O of com.mojang.serialization.MapCodec.forGetter?, @[FlexibleNullability] A of com.mojang.serialization.MapCodec?>? declared in com.mojang.serialization.MapCodec' type=@[FlexibleNullability] com.mojang.serialization.codecs.RecordCodecBuilder<@[FlexibleNullability] O of com.mojang.serialization.MapCodec.forGetter?, @[FlexibleNullability] A of com.mojang.serialization.MapCodec?>? origin=null
                      TYPE_ARG O: com.martmists.serialization.TestData
                      ARG <this>: CALL 'public open fun fieldOf (p0: @[FlexibleNullability] kotlin.String?): @[FlexibleNullability] com.mojang.serialization.MapCodec<@[FlexibleNullability] A of com.mojang.serialization.Codec?>? declared in com.mojang.serialization.Codec' type=@[FlexibleNullability] com.mojang.serialization.MapCodec<@[FlexibleNullability] A of com.mojang.serialization.Codec?>? origin=null
                        ARG <this>: GET_FIELD 'FIELD IR_EXTERNAL_JAVA_DECLARATION_STUB name:INT type:@[FlexibleNullability] com.mojang.serialization.codecs.PrimitiveCodec<@[FlexibleNullability] kotlin.Int?>? visibility:public [final,static] declared in com.mojang.serialization.Codec' type=@[FlexibleNullability] com.mojang.serialization.codecs.PrimitiveCodec<@[FlexibleNullability] kotlin.Int?>? origin=null
                        ARG p0: CONST String type=kotlin.String value="x"
                      ARG p0: RAW_FUNCTION_REFERENCE 'public final fun <get-x> (): <http://kotlin.Int|kotlin.Int> declared in com.martmists.serialization.TestData' type=kotlin.Any
                    ARG p1: CALL 'public final fun forGetter <O> (p0: @[FlexibleNullability] java.util.function.Function<@[FlexibleNullability] O of com.mojang.serialization.MapCodec.forGetter?, @[FlexibleNullability] A of com.mojang.serialization.MapCodec?>?): @[FlexibleNullability] com.mojang.serialization.codecs.RecordCodecBuilder<@[FlexibleNullability] O of com.mojang.serialization.MapCodec.forGetter?, @[FlexibleNullability] A of com.mojang.serialization.MapCodec?>? declared in com.mojang.serialization.MapCodec' type=@[FlexibleNullability] com.mojang.serialization.codecs.RecordCodecBuilder<@[FlexibleNullability] O of com.mojang.serialization.MapCodec.forGetter?, @[FlexibleNullability] A of com.mojang.serialization.MapCodec?>? origin=null
                      TYPE_ARG O: com.martmists.serialization.TestData
                      ARG <this>: CALL 'public open fun fieldOf (p0: @[FlexibleNullability] kotlin.String?): @[FlexibleNullability] com.mojang.serialization.MapCodec<@[FlexibleNullability] A of com.mojang.serialization.Codec?>? declared in com.mojang.serialization.Codec' type=@[FlexibleNullability] com.mojang.serialization.MapCodec<@[FlexibleNullability] A of com.mojang.serialization.Codec?>? origin=null
                        ARG <this>: GET_FIELD 'FIELD IR_EXTERNAL_JAVA_DECLARATION_STUB name:STRING type:@[FlexibleNullability] com.mojang.serialization.codecs.PrimitiveCodec<@[FlexibleNullability] kotlin.String?>? visibility:public [final,static] declared in com.mojang.serialization.Codec' type=@[FlexibleNullability] com.mojang.serialization.codecs.PrimitiveCodec<@[FlexibleNullability] kotlin.String?>? origin=null
                        ARG p0: CONST String type=kotlin.String value="y"
                      ARG p0: RAW_FUNCTION_REFERENCE 'public final fun <get-y> (): kotlin.String declared in com.martmists.serialization.TestData' type=kotlin.Any
            FUNCTION_REFERENCE 'public final fun <anonymous> (it: kotlin.Any): kotlin.Any declared in com.martmists.serialization.TestData.Companion.<init>' type=kotlin.Any origin=LAMBDA reflectionTarget=null
Code:
Copy code
@OptIn(UnsafeDuringIrConstructionAPI::class)
class CodecIRGenerator : IrGenerationExtension {
    override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
        val recordAnnotation = FqName("com.martmists.serialization.Record")

        moduleFragment.files.forEach { file ->
            file.declarations.filterIsInstance<IrClass>().forEach { klass ->
                if (!klass.annotations.hasAnnotation(recordAnnotation)) return@forEach

                if (!klass.isData) {
                    pluginContext.reportError(
                        klass,
                        "@Record can only be applied to data classes"
                    )
                    return@forEach
                }

                val companion = klass.declarations.filterIsInstance<IrClass>().firstOrNull { it.isCompanion } ?: createCompanionObject(klass, pluginContext)

                if (companion.declarations.any { it is IrField && it.name.asString() == "CODEC" }) {
                    pluginContext.reportError(
                        companion,
                        "@Record classes should not have a CODEC field"
                    )
                }

                addCodecField(klass, companion, pluginContext)
            }
        }
    }

    private fun createCompanionObject(
        klass: IrClass,
        pluginContext: IrPluginContext
    ): IrClass {
        klass.companionObject()
        val companion = pluginContext.irFactory.buildClass {
            startOffset = klass.startOffset
            endOffset = klass.endOffset
            name = Name.identifier("Companion")
            isCompanion = true
            kind = ClassKind.OBJECT
        }
        companion.createThisReceiverParameter()
        companion.addConstructor {
            isPrimary = true
        }.apply {
            body = pluginContext.irFactory.createExpressionBody(
                companion.startOffset,
                companion.endOffset,
                IrBlockBuilder(pluginContext, Scope(symbol), klass.startOffset, klass.endOffset).buildStatement(
                    klass.startOffset,
                    klass.endOffset
                ) {
                    irReturnUnit()
                }
            )
        }
        companion.parent = klass
        klass.declarations.add(companion)
        return companion
    }

    private fun addCodecField(
        klass: IrClass,
        companion: IrClass,
        pluginContext: IrPluginContext,
    ) {
        val ctor = klass.primaryConstructor ?: run {
            pluginContext.reportError(klass, "@Record requires a primary constructor")
            return
        }

        val field = pluginContext.irFactory.buildField {
            startOffset = klass.startOffset
            endOffset = klass.endOffset
            name = Name.identifier("CODEC")
            type = pluginContext.irBuiltIns.anyType
        }

        val codecBuilderKlass = pluginContext.referenceClass(
            ClassId(
                FqName("com.mojang.serialization.codecs"),
                Name.identifier("RecordCodecBuilder")
            )
        )!!
        val codecBuilderInstanceKlass = pluginContext.referenceClass(
            ClassId(
                FqName("com.mojang.serialization.codecs"),
                FqName("RecordCodecBuilder.Instance"),
                false,
            )
        )!!
        val codecKlass = pluginContext.referenceClass(
            ClassId(
                FqName("com.mojang.serialization"),
                Name.identifier("Codec"),
            )
        )!!
        val mapCodecKlass = pluginContext.referenceClass(
            ClassId(
                FqName("com.mojang.serialization"),
                Name.identifier("MapCodec"),
            )
        )!!
        val appKlass = pluginContext.referenceClass(
            ClassId(
                FqName("com.mojang.datafixers"),
                FqName("Products.P${ctor.nonDispatchParameters.size}"),
                false,
            )
        )!!

        val create = codecBuilderKlass.functions.first { it.owner.name.asString() == "create" }
        val group = codecBuilderInstanceKlass.functions.first {
            it.owner.name.asString() == "group" && it.owner.nonDispatchParameters.size == ctor.parameters.size
        }
        val fieldOf = codecKlass.functions.first { it.owner.name.asString() == "fieldOf" }
        val optionalFieldOf = codecKlass.functions.first { it.owner.name.asString() == "optionalFieldOf" }
        val forGetter = mapCodecKlass.functions.first { it.owner.name.asString() == "forGetter" }
        val apply = appKlass.functions.first { it.owner.name.asString() == "apply" }

        field.parent = companion
        field.initializer = pluginContext.irFactory.createExpressionBody(
            IrBlockBuilder(pluginContext, Scope(companion.symbol), klass.startOffset, klass.endOffset).buildStatement(
                klass.startOffset,
                klass.endOffset
            ) {
                irCall(create).apply {
                    typeArguments[0] = klass.defaultType
                    val lambda = pluginContext.irFactory.addFunction(
                        companion,
                    ) {
                        returnType = pluginContext.irBuiltIns.anyType
                        name = Name.special("<anonymous>")
                    }

                    val param = pluginContext.irFactory.buildValueParameter(
                        IrValueParameterBuilder().apply {
                            type = pluginContext.irBuiltIns.anyType
                            name = Name.identifier("it")
                        },
                        lambda,
                    )

                    lambda.parent = companion
                    lambda.parameters = listOf(param)

                    lambda.body = pluginContext.irFactory.createExpressionBody(companion.startOffset, companion.endOffset, IrBlockBuilder(pluginContext, Scope(lambda.symbol), klass.startOffset, klass.endOffset).buildStatement(companion.startOffset, companion.endOffset) {
                        val builder = irGet(param)

                        val product = irCall(group).apply {
                            dispatchReceiver = builder
                            ctor.nonDispatchParameters.forEachIndexed { index, arg ->
                                typeArguments[index] = arg.type
                            }
                            arguments.addAll(1,
                                ctor.nonDispatchParameters.map { arg ->
                                    val codec: IrExpression = when {
                                        arg.type == pluginContext.irBuiltIns.intType -> {
                                            irGetField(
                                                null,
                                                codecKlass.owner.declarations.filterIsInstance<IrProperty>()
                                                    .first { it.name.asString() == "INT" }.backingField!!
                                            )
                                        }

                                        arg.type == pluginContext.irBuiltIns.stringType -> {
                                            irGetField(
                                                null,
                                                codecKlass.owner.declarations.filterIsInstance<IrProperty>()
                                                    .first { it.name.asString() == "STRING" }.backingField!!
                                            )
                                        }

                                        arg.type.erasedUpperBound == pluginContext.irBuiltIns.listClass -> {
                                            TODO()
                                        }

                                        else -> TODO()
                                    }

                                    val field = if (arg.hasDefaultValue()) {
                                        irCall(optionalFieldOf).apply {
                                            dispatchReceiver = codec
                                            arguments.addAll(1,
                                                listOf(
                                                    irString(arg.name.asString()),
                                                    arg.defaultValue!!.expression,
                                                )
                                            )
                                        }
                                    } else {
                                        irCall(fieldOf).apply {
                                            dispatchReceiver = codec
                                            arguments.addAll(1,
                                                listOf(
                                                    irString(arg.name.asString()),
                                                )
                                            )
                                        }
                                    }

                                    irCall(forGetter).apply {
                                        typeArguments[0] = klass.defaultType
                                        dispatchReceiver = field
                                        arguments.addAll(1,
                                            listOf(
                                                irRawFunctionReference(
                                                    pluginContext.irBuiltIns.anyType,
                                                    klass.properties.first { it.name == arg.name }.getter!!.symbol
                                                )
                                            )
                                        )
                                    }
                                }
                            )
                        }

                        irCall(apply).apply {
                            typeArguments[0] = klass.defaultType
                            dispatchReceiver = product
                            arguments.addAll(
                                listOf(
                                    builder,
                                    irRawFunctionReference(pluginContext.irBuiltIns.anyType, ctor.symbol)
                                )
                            )
                        }
                    })

                    // TODO: Find the correct irFactory method for this
                    @Suppress("INVISIBLE_REFERENCE")
                    arguments[0] = IrFunctionExpressionImpl(null, companion.startOffset, companion.endOffset, pluginContext.irBuiltIns.anyType, IrStatementOrigin.LAMBDA, lambda)
                }
            }
        )
        companion.declarations.add(field)
    }

    private fun IrPluginContext.reportError(target: IrDeclaration, message: String) {
        // TODO: <http://diagnosticReporter.at|diagnosticReporter.at>(target).report()
        throw Exception(message)
    }
}
y
I think there's like an `IrFunctionExpression`or something like that, but I can't remember. An easy way to check would be to add a lambda into the source code, and dump the IR for that source code inside your plugin, and thus replicate it
m
How do I dump the source for a lambda? The bytecode viewer seems to only show JVM bytecode. Also, IrFunctionExpression is abstract, hence why I use IrFunctionExpressionImpl.
y
IrElement.dump()
or something like that. Or just use the debugger and view the IR tree in your
IrGenerationExtension
m
How can I attach a debugger? I've tried to add flags as found in a different thread but the kotlin daemon reports that "server=n" is an unknown flag. I tried println(klass.dump()) but prints aren't being logged when I run a gradle build it seems.
y
Simplest way would be to add tests into your plugin. There's a compiler plugin template on GitHub that had the test infra set up already. Also, I'd just throw an error with the dump as the message lol
m
The only real difference I see is that my code adds
Copy code
FUNCTION_REFERENCE 'public final fun <anonymous> (it: com.mojang.serialization.codecs.RecordCodecBuilder.Instance<com.martmists.serialization.TestData>): com.mojang.datafixers.Products.P2<F of com.mojang.datafixers.Products.P2, T1 of com.mojang.datafixers.Products.P2, T2 of com.mojang.datafixers.Products.P2> declared in com.martmists.serialization.TestData.Companion.<init>' type=kotlin.Any origin=LAMBDA reflectionTarget=null
but I don't know how to pass it as parameter without the FUNCTION_REFERENCE being added
Okay I seem to have gotten it working; Is there a way for the generated companion and/or fields can be visible to IntelliJ? I already added a FIR registrar and I can write code as if they exist and it compiles just fine, but they show as unresolved references in IntelliJ
y
m
I did this, but it only seems to work when the plugin is applied to the root project, when applied to a few subprojects only it doesn't seem to work.
nevermind, the project I use it in uses java 23 but intellij seems to only support classes compiled for java 21. I'll have to wait for an IJ update.