Hi. Is it possible to get the default value of an ...
# compiler
j
Hi. Is it possible to get the default value of an
IrValueParameter
? I’ve tried
IrValueParameter#defaultValue
but it always returns null.
m
Where do you access this property? Or more specific in what extension / compiler phase?
I tried do reproduce it. So I checked the default values of some function parameters when visiting the functions in
visitSimpleFunction
of
IrElementVisitorVoid
in an
IrGenerationExtension
but it just worked.
j
Hi MerlinTHS, thanks for the reply. I implemented it with the same path as you, I'll try again. 🙏
Hi @MerlinTHS. I have a function like this.
Copy code
@Composable
public fun Success(text: String = "SUCCESS") {
    Box(
        modifier = Modifier.fillMaxWidth(),
        contentAlignment = Alignment.Center,
    ) {
        Text(text = text)
    }
}
My
IrElementVisitorVoid
implementation looks like this.
Copy code
internal class SugarIrVisitor(
    private val logger: Logger,
    private val addSugarIrData: (data: SugarIrData) -> Unit,
) : IrElementVisitorVoid {
    override fun visitModuleFragment(declaration: IrModuleFragment) {
        declaration.files.forEach { file ->
            file.accept(this, null)
        }
    }

    override fun visitFile(declaration: IrFile) {
        declaration.declarations.forEach { item ->
            item.accept(this, null)
        }
    }

    override fun visitSimpleFunction(declaration: IrSimpleFunction) {
        if (declaration.name.asString() == "Success") {
            val defaultValue = declaration.valueParameters.first().defaultValue!!
            logger.warn("Success function was copied")
            addSugarIrData(SugarIrData(defaultValue = defaultValue))
        }
    }
}
But I get an NPE at
val defaultValue = declaration.valueParameters.first().defaultValue!!
. Where did I go wrong in my
IrElementVisitorVoid
implementation?
Oh.. in this case, “SUCCESS” default value is not an
IrExpressionBody
but an
IrExpression
? Then it makes sense that an NPE occurred (
IrValueParameter#defaultValue
is of type
IrExpressionBody
).
m
Every
defaultValue
is of type
IrExpressionBody
. You can find the actual expression you’re passing into it ( which in this case is of type
IrConst
) in the
expression
property of the
defaultValue
. Can you make sure, that the accessed parameter is actually one you declared in the original signature and not something the Compose Compiler Plugin generates in its lowerings like
$composer
or
$changed
? Something like:
Copy code
fun List<IrValueParameter>.filterGenerated() =
    filterNot { it.name.asString().startsWith("$") }
j
Thanks for the detailed explanation. I changed the code to this, but all defaultValues are output as null.
Copy code
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
    if (declaration.name.asString() == "Success") {
        declaration.valueParameters.forEach { param ->
            logger.warn("name: ${param.name.asString()}, defaultValue: ${param.defaultValue}")
        }
    }
}
Copy code
@Composable
public fun Success(text: String = "SUCCESS", really: Boolean = true) {
    Box(
        modifier = Modifier.fillMaxWidth(),
        contentAlignment = Alignment.Center,
    ) {
        Text(text = text + really.toString())
    }
}
Oh... I replaced the function with a normal function and it works fine. Is this a bug in the Compose Compiler?
m
The Compose Plugin transforms the function to be recomposable. I don't know what's the exact purpose of removing the default values from the original parameter but for sure they aren't lost - only integrated in Composes recomposition system.
j
That’s right, defaultValue works fine in Compose. I’ll investigate further why the defaultValue of the Composable function is showing as null. Thanks.
m
Maybe someone in this channel knows more about how Compose handles default parameters - feel free to ask. Another possible approach would be visiting the `defaultValue`s before Compose lowers them.
You can find more information about how and why Compose lowers this kind of stuff in a comment in one of its main transformers ( https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt )
Copy code
Default Arguments
* =================
*
* Composable functions need to have the default expressions executed inside of the group of the
* function. In order to accomplish this, composable functions handle default arguments
* themselves, instead of using the default handling of kotlin. This is also a win because we can
* handle the default arguments without generating an additional function since we do not need to
* worry about callers from java. Generally speaking though, compose handles default arguments
* similarly to kotlin in that we generate a $default bitmask parameter which maps each parameter
* index to a bit on the int. A value of "1" for a given parameter index indicated that that
* value was *not* provided at the callsite, and the default expression should be used instead.
*
*     @Composable fun A(x: Int = 0) {
    *       f(x)
    *     }
*
* gets transformed into
*
*     @Composable fun A(x: Int, $default: Int) {
    *       val x = if ($default and 0b1 != 0) 0 else x
    *       f(x)
    *     }
*
* Note: This transform requires [ComposerParamTransformer] to also be run in order to work
* properly.
*
j
Another possible approach would be visiting the `defaultValue`s before Compose lowers them.
I want to try that way. Can I adjust the order of application of the Compiler Plugin? (Maybe FIR? (First Intermediate Representation)… actually I don’t know what FIR is. 😅)
m
So accessing the default parameters after Compose has lowered them means to take a look at the body.
j
Oh yeah... I remember seeing something about that when I was studying the internals of the Compose compiler. It’s been a while since I studied the internals, so I forgot about it for a while.
Thanks for the reference link.
m
FIR means "Frontend Intermediate Representation". This channel already contains a lots of information about the new frontend ( which uses FIR ). If you have more questions about it, I would recommend to ask someone actually working at the new frontend ( like @dmitriy.novozhilov).
j
WOW... Thanks again. I’ve been trying to figure out what the F stood for, and it’s Frontend!
m
Maybe it's enough to register your extension point ( I think it was
IrGenerationExtension
) with one of the predefined loading orders ( FIRST ).
Copy code
public final class LoadingOrder {
    public static final LoadingOrder ANY = new LoadingOrder();
    public static final LoadingOrder FIRST = new LoadingOrder("first");
    public static final LoadingOrder LAST = new LoadingOrder("last");

    ...
}
To do that, you have to use the old and deprecated
ComponentRegistrar
. It allows you to access the
extensionArea
of
MockProject
( which I think was the reason to deprecate it ) where you can specify the order when registering an extension.
Copy code
class YourComponentRegistrar : ComponentRegistrar {
    override val supportsK2: Boolean
        get() = false // Except you're using FIR

    override fun registerProjectComponents(
        project: MockProject,
        configuration: CompilerConfiguration
    ) {
        project.extensionArea.getExtensionPoint(IrGenerationExtension.extensionPointName)
            .registerExtension(YourIrGenerationExtension(), LoadingOrder.FIRST, project)
    }
}
j
Oh thanks! I’ll try again.
It works! Thank you so much. If I were closer, I’d treat you to a meal. Have a great day! 🙇‍♂️