I am getting this error when I try to create an `I...
# compiler
j
I am getting this error when I try to create an
IrReturn
which uses a
IrCall
as a value. I am creating this
IrCall
from a function.
Copy code
Caused by: java.lang.AssertionError: SyntheticAccessorLowering should not attempt to modify other files!
While lowering this file: FILE fqName:com.javiersc.playground fileName:__GENERATED DECLARATIONS__.kt
Trying to add this accessor: FUN SYNTHETIC_ACCESSOR name:access$resolve visibility:public modality:FINAL <> ($context_receiver_0:kotlin.String) returnType:kotlin.String
Any clue about what I am doing wrong?
t
I've seen lowering errors mostly when I did not set the function receivers properly. That would be the first thing I would check.
j
Copy code
public fun IrSimpleFunction.toIrCall(
    startOffset: Int = UNDEFINED_OFFSET,
    endOffset: Int = UNDEFINED_OFFSET,
    symbol: IrSimpleFunctionSymbol = this.symbol,
    type: IrType = returnType,
    typeArgumentsCount: Int = typeParameters.size,
    valueArgumentsCount: Int = valueParameters.size,
    origin: IrStatementOrigin? = null,
    superQualifierSymbol: IrClassSymbol? = null,
    block: IrCall.() -> Unit = {},
): IrCall =
    IrCallImpl(
            startOffset = startOffset,
            endOffset = endOffset,
            type = type,
            symbol = symbol,
            typeArgumentsCount = typeArgumentsCount,
            valueArgumentsCount = valueArgumentsCount,
            origin = origin,
            superQualifierSymbol = superQualifierSymbol,
        )
        .apply {
            val functionValueParameters: List<IrValueParameter> = this@toIrCall.valueParameters
            for ((index: Int, param: IrValueParameter) in functionValueParameters.withIndex()) {
                putValueArgument(index, param.toIrGetValue())
            }
            block()
        }
The problem I have is the generated IR is the same than a function call that I have written to compare, so it is hard to find the issue
t
Do you set the call receiver somewhere else? If you don't, it won't work.
This is a utility function I use to build calls, similar to yours. You can see that I pass two receivers to it.
Copy code
fun irCall(
        symbol: IrFunctionSymbol,
        origin: IrStatementOrigin? = null,
        dispatchReceiver: IrExpression? = null,
        extensionReceiver: IrExpression? = null,
        vararg args: IrExpression
    ): IrCallImpl {
        return IrCallImpl(
            UNDEFINED_OFFSET,
            UNDEFINED_OFFSET,
            symbol.owner.returnType,
            symbol as IrSimpleFunctionSymbol,
            symbol.owner.typeParameters.size,
            symbol.owner.valueParameters.size,
            origin
        ).also {
            if (dispatchReceiver != null) it.dispatchReceiver = dispatchReceiver
            if (extensionReceiver != null) it.extensionReceiver = extensionReceiver
            args.forEachIndexed { index, arg ->
                it.putValueArgument(index, arg)
            }
        }
    }
I think I've... appropriated this from Compose, they have a utility class which contains a lot of useful IR stuff.
j
the one I wrote manually has no
dispatchReceiver
, I will add those two, but probably they are null, let me check
yep, both are null
Generated:
Copy code
CALL 'private final fun resolve ($context_receiver_0: kotlin.String): kotlin.String declared in com.javiersc.playground' type=kotlin.String origin=null
  $context_receiver_0: GET_VAR '$this$with: kotlin.String declared in com.javiersc.playground.resolve.<anonymous>' type=kotlin.String origin=null
Manually written:
Copy code
CALL 'private final fun resolve2 ($context_receiver_0: kotlin.String): kotlin.String declared in com.javiersc.playground' type=kotlin.String origin=null
  $context_receiver_0: GET_VAR '$this$with: kotlin.String declared in com.javiersc.playground.resolve2.<anonymous>' type=kotlin.String origin=null
t
Isn't there a $this under that call line for the generated one?
Like here:
Copy code
CALL 'public final fun provideDelegate (...), <http://kotlin.Int|kotlin.Int>> origin=null
        $this: CALL 'public final fun int (default: <http://kotlin.Int|kotlin.Int>, min: <http://kotlin.Int|kotlin.Int>?, max: <http://kotlin.Int?|kotlin.Int?>):
j
nop, that is all
This is the function I am trying to create the call
Copy code
FUN name:resolve visibility:private modality:FINAL <> ($context_receiver_0:kotlin.String) returnType:kotlin.String
  annotations:
    ContextResolution
  contextReceiverParametersCount: 1
  VALUE_PARAMETER name:$context_receiver_0 index:0 type:kotlin.String
  BLOCK_BODY
    RETURN type=kotlin.Nothing from='private final fun resolve ($context_receiver_0: kotlin.String): kotlin.String declared in com.javiersc.playground'
      GET_VAR '$context_receiver_0: kotlin.String declared in com.javiersc.playground.resolve' type=kotlin.String origin=null
And the written manually
Copy code
FUN name:resolve2 visibility:private modality:FINAL <> ($context_receiver_0:kotlin.String) returnType:kotlin.String
  contextReceiverParametersCount: 1
  VALUE_PARAMETER name:$context_receiver_0 index:0 type:kotlin.String
  BLOCK_BODY
    RETURN type=kotlin.Nothing from='private final fun resolve2 ($context_receiver_0: kotlin.String): kotlin.String declared in com.javiersc.playground'
      GET_VAR '$context_receiver_0: kotlin.String declared in com.javiersc.playground.resolve2' type=kotlin.String origin=null
create the
$this$with
part was tricky, but even hardcoding a
"foo".toIrConst(...)
is giving me the same fail
t
I really think that's the source of the problem. At least I had a 10+ hours debug session with $this and <this>.
j
but if I remove it and just hardcode that, I got the same issue
t
Well, I'm out of ideas to be honest. Is this a function with
context(String)
?
j
yeah, indeed, I am not calling the generated function, I am calling it inside of the generated function, the original one (
resolve
)
Copy code
context(String)
@ContextResolution private fun resolve(): String {
    return this@String
}

context(String)
private fun resolve2(): String {
    return this@String
}
Copy code
context(String)
fun resolve(): String {
    return this@String
}

// I am trying to generate this
fun resolve(foo: ContextualProvider<String> = Foo_Factory): String {
    with(foo.get()) {
        return resolve()
    }
}
t
Copy code
$context_receiver_0: GET_VAR '$this$with: kotlin.String declared in com.javiersc.playground.resolve.<anonymous>'
My guess is that in the line above the
.resolve
is not the right one. Try renaming one of the functions.
That won't help but maybe the generated code will be clearer.
But I don't want to lead you to wrong directions. 🙂 I'm nowhere close to a real expert.
j
don't worry, me neither 😛
there is only one resolve, the other one is generated, so that
resolve
should be the only one, I am going to check
t
But wait, in your example there were two resolves. One with the
foo
and one without.
j
well, you are right, the generated one is there from FIR, but I am passing the original one, not the generated
Copy code
this.extensionReceiverParameter = getValueIrValueParameter
val originalContextResolutionCall: IrCall =
    originalContextResolutionFunction.toIrCall {
        val getValueArgument: IrGetValue = getValueArgument(0).asIr()!!
        val updatedGetValueArgument: IrGetValue =
            getValueArgument.symbol.owner
                .deepCopyWithSymbols(this@createLambdaIrSimpleFunction)
                .apply { this.name = "\$this\$with".toName() }
                .asIr<IrValueParameter>()!!
                .toIrGetValue()
        putValueArgument(0, updatedGetValueArgument)
    }
val originalContextResolutionIrReturn: IrReturn =
    createIrReturn(
        type = irBuiltIns.nothingType,
        returnTargetSymbol = originalContextResolutionFunction.symbol,
        value = originalContextResolutionCall
    )
this.body = createIrBlockBody {
    this.statements.add(originalContextResolutionIrReturn)
}
t
It's always the receivers. ;)
j
but, I am passing the correct one
and the original one has no receivers, as the
resolve2
equivalent 🤔
maybe the receiver in the
with
lambda?
t
So, are you trying to replace the call to the
context(String) resolve()
with a call to
resolve(foo...)
?
j
No, I am not replacing
Copy code
context(String)
fun resolve(): String {
    return this@String
}

// generated with no body
fun resolve(foo: ContextualProvider<String> = Foo_Factory): String

// add body
fun resolve(foo: ContextualProvider<String> = Foo_Factory): String {
    with(foo.get()) {
        return resolve() // call to `resolve()` with context receiver
    }
}
oh my, I think I know where is the problem, resolve function is private, and the generated one is generated in a different file automatically by FIR
t
Let me know if that solves it.
j
Yeah, I was now in a different place!
Copy code
Non-mapped local declaration: VALUE_PARAMETER name:$this$with index:0 type:kotlin.String
Do you have any utility to create those
$this
?
I think this is not valid:
Copy code
val updatedGetValueArgument: IrGetValue =
    getValueArgument.symbol.owner
        .deepCopyWithSymbols(this@createLambdaIrSimpleFunction)
        .apply { this.name = "\$this\$with".toName() }
        .asIr<IrValueParameter>()!!
        .toIrGetValue()
t
As far as I know you can't really build symbols like that, they should be bound. But I know mostly backend IR, no idea if that would work in FIR. You actually don't need the
with(foo.get())
I think, you could just set it's value to the context receiver parameter.
Something like this: call.putValueArgument(0, irCall( <foo.get> ))
j
I am putting the receiver in a similar way, looks like using the same works:
Copy code
val getValueIrValueParameter: IrValueParameter =
    parameter.deepCopyWithSymbols(this@createLambdaIrSimpleFunction).apply {
        this.name = "\$this\$with".toName()
        this.type = getFunction.returnType
        this.index = -1
        this.defaultValue = null
    }
this.extensionReceiverParameter = getValueIrValueParameter
val originalContextResolutionCall: IrCall =
    originalContextResolutionFunction.toIrCall {
        putValueArgument(0, getValueIrValueParameter.toIrGetValue())
    }
But I am not sure if I should put the
extensionReceiverParameter
so or I should do it in a different way
anyway, I am going to try if it is working on the IDE 😛
Well, it is semi-working, on tests both use cases are working, on the IDE calling
box()
works, calling
box2()
fails
Copy code
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
	at com.javiersc.kotlin.inject.playground.__GENERATED_DECLARATIONS__Kt.resolve2(__GENERATED DECLARATIONS__.kt)
	at com.javiersc.kotlin.inject.playground.__GENERATED_DECLARATIONS__Kt.resolve2$default(__GENERATED DECLARATIONS__.kt)
	at com.javiersc.kotlin.inject.playground.MainKt.box2(Main.kt:16)
	at com.javiersc.kotlin.inject.playground.MainKt.main(Main.kt:7)
t
That seems like a quite different problem.
j
yep, totally
debugging plugins were not trivial I think, are you doing it?
transitive dependency issue when publishing to maven local
it is working now!
java_2023-07-19_14-24-29.gif
t
Great! Yes, I do debug the box tests.
j
yeah, with tests I have no problem, indeed more than development I am doing debug development, I am more in the evaluation screen than in the normal file 🤣 it is more about debugging a plugin applied on a project
Hey! I am getting a similar issue, but this time I don't have context receivers, again with the
$this$with
.
Copy code
class Bur_Factory(
    private val contextualProviderString: ContextualProvider<String>,
) : ContextualFactory<Int> {

    override fun get(): Int {
        with(contextualProviderString.get()) {
            return bar()
        }
    }
}
As you recommended me to avoid deep copy with
deepCopyWithSymbols
how would you add the receiver to the
with
lambda?
contextualProviderString.get()
type is
String
, and with the copy approach I am getting
Non-mapped local declaration: VALUE_PARAMETER name:$this$with type:kotlin.String
But I can't set the
call
to
get
as receiver in the lambda, that is the reason I am building a
$this$with
value parameter manually
t
@Javier Uh-oh, sorry, haven't seen the message. I'm a slack noob. I would pass the result of
contextualProviderString.get()
to
bar
directly. The
with
is just syntactical sugar so the programmer can tell the compiler which receivers to use. If I'm guessing right, the context receivers are just the first N parameters of the function and there is nothing really special about them. This is from
IrCallImpl
code:
Copy code
valueArgumentsCount: Int = symbol.descriptor.valueParameters.size + symbol.descriptor.contextReceiverParameters.size,
So, I guess they are just go in as normal value arguments.
thank you color 1
j
Interesting, I didn’t know that, so I will be able to just remove all with lambdas as I can put them as receivers or as context receivers, I will do some tests and I will confirm you
I am doing something wrong with the receivers, as I am getting the mapping error the map has
Copy code
VALUE_PARAMETER name:<this> type:com.javiersc.kotlin.inject.ContextualProvider<T of com.javiersc.kotlin.inject.ContextualProvider>
but I am getting
Copy code
VALUE_PARAMETER name:string index:0 type:com.javiersc.kotlin.inject.ContextualProvider<kotlin.String>
I am picking the valueParameters from the primary constructor and converting them to
IrGetValue
I could deep copy it and start to fix it in a manual way, but I am not sure what I am missing to avoid doing that
Note: I have renamed
contextualProviderString
to
string
Well, I solved it without doing manual things by by using the getter as receiver
Copy code
this.dispatchReceiver = providerClass.getGetterIrCall(parameter)
Copy code
fun IrClass.getGetterIrCall(valueParameter: IrValueParameter): IrCall =
        declarationPropertyGetter(valueParameter).getter!!.toIrCall(
            type = valueParameter.type,
            origin = IrStatementOrigin.GET_PROPERTY
        ) {
            this.dispatchReceiver = getFunctionGetValue
            this.extensionReceiver = null
        }
t
Seems like a good solution (if it works 🙂 ). I tend to use deepCopy very rarely because, frankly, I don't exactly understand what it does with all the symbols. I should spend time on exploring it, but... time.
j
I didn’t need to deep copy as using the getter from the field instead of value parameter did the trick, but I am not sure if that approach will be valid in the other sample I shared with functions, as there are no fields there, and I was unable to use directly the value parameter as IrGetValue successfully, I will try later anyway thank you 🙏 most of the complex in the algorithm was nesting the with, so no only 80-90% less code, the hard part has been removed!
105 Views