HellRevenger
12/26/2025, 3:44 AM// code is in subclass of FirDeclarationGenerationExtension, generateFunctions()
buildSimpleFunction { // buildSimpleFunction become buildNamedFunction in latest kotlin, also tried buildSimpleFunctionCopy
val param: FirTypeParameterSymbol = ... // get from original function
// first way
val newParam1 = buildTypeParameterCopy(param) {
symbol = FirTypeParameterSymbol()
containingDeclarationSymbol = this@buildSimpleFunction.symbol
origin = Key.origin
resolvePhase = FirResolvePhase.BODY_RESOLVE
moduleData = session.moduleData
}
typeParameters.add(newParam1)
// second way
val newParam2 = param
typeParameters.add(newParam2)
}
First way is learned from FunctionCopyUtils .
With first way, Fir2Ir throws the following Exception during compiling the function while resolving return type
Caused by: java.lang.IllegalStateException: Cannot find cached type parameter by FIR symbol: E of the owner: FirRegularClassSymbol java/util/List
at org.jetbrains.kotlin.fir.backend.Fir2IrClassifierStorage.getIrTypeParameterSymbol(Fir2IrClassifierStorage.kt:157)
at org.jetbrains.kotlin.fir.backend.utils.SymbolConversionUtilsKt.toIrSymbol(SymbolConversionUtils.kt:51)
at org.jetbrains.kotlin.fir.backend.Fir2IrTypeConverter.toIrType(Fir2IrTypeConverter.kt:120)
With second way, I can compile the function and see the generated source of the function (see attachments 1 and 2), but FIR refuse to resolve the function in IDE (see attachment 3) when I try to invoke it, and compile it also has the same error as what IDE tell.
What am I doing wrong?Caleb B
12/26/2025, 3:51 AMreturn mutableListOf().apply { }. Does it work when you specify the type param?HellRevenger
12/26/2025, 3:54 AMCaleb B
12/26/2025, 3:56 AMidea.log file say when the error occurs in the IDE?Caleb B
12/26/2025, 4:00 AMHellRevenger
12/26/2025, 4:01 AMCaleb B
12/26/2025, 4:06 AMHellRevenger
12/26/2025, 4:12 AMsimple.fir.txt or i can generate it from whereCaleb B
12/26/2025, 4:13 AM.fir.txt will be autogenerated for it when the test is run. You can also delete the existing .fir.txt for a given test kt and a new one will be created.Caleb B
12/26/2025, 4:16 AM<name>.kt.bak, so the breakpoint doesn't trigger for anything except the one thing you're trying to inspect.test Gradle task once to make it recognize all of your test files, then open the test file you want to run with the Kotlin Compiler Devkit extension installed, and you'll see a "run" icon to just run that one file.Caleb B
12/26/2025, 4:21 AMHellRevenger
12/26/2025, 4:32 AM<Unresolved name: fakeGet> in .fir.txt, and then it doesn't generate fir.ir.txt since .fir.txt is different from expectedCaleb B
12/26/2025, 4:37 AMthen it doesn't generateIf you delete thesincefir.ir.txtis different from expected.fir.txt
.fir.txt file for the test, it'll be forced to generate a new one.HellRevenger
12/26/2025, 4:39 AMHellRevenger
12/26/2025, 4:39 AM.fir.txt but no fir.ir.txtCaleb B
12/26/2025, 4:39 AMHellRevenger
12/26/2025, 4:47 AM.fir.txt is R|kotlin/collections/listOf|<R|kotlin/String|>(String()).R|foo/bar/fakeGet<None of the following candidates is applicable because of receiver type mismatch: [foo/bar/fakeGet]>#|<R|ExplicitTypeArgumentIfMadeFlexibleSyntheticallyTypeAttribute(coneType=kotlin/String, relevantFeature=null) kotlin/String!|>(Int(0))
for both wayCaleb B
12/26/2025, 4:48 AMHellRevenger
12/26/2025, 4:49 AM.fir.txt ?Caleb B
12/26/2025, 4:51 AMclass Foo<T> {
// Given this function:
fun someFunction()
}
// Generate this:
fun <T> Foo<T>.someFunction_fake() {
this.someFunction<T>()
}HellRevenger
12/26/2025, 4:52 AMclass Foo<T> {
// Given this function:
fun someFunction(): T
}
// Generate this:
fun <T> Foo<T>.someFunction_fake(): T {
return this.someFunction()
}HellRevenger
12/26/2025, 4:53 AMCaleb B
12/26/2025, 4:53 AMHow are you getting the param?Copy codeval param: FirTypeParameterSymbol = ... // get from original function
Caleb B
12/26/2025, 4:53 AMCaleb B
12/26/2025, 4:56 AMcopyFirFunctionWithResolvePhase on its own, and then adding a receiver parameter and changing the sourcesHellRevenger
12/26/2025, 4:58 AMval classSymbol = session.symbolProvider.getClassLikeSymbolByClassId(ClassId.fromString("java.util.List")) as? FirClassSymbol
?: return@let
classSymbol.typeParameterSymbols.forEach { typeParameterSymbol ->
typeParameterSymbol // <-- here
}HellRevenger
12/26/2025, 4:58 AMval scope = classSymbol.unsubstitutedScope(session, ScopeSession(), true, null)
scope.getFunctions(Name.identifier("fakeGate")).forEach { firFunction ->
firFunction.dispatchReceiverType?.let {
it.typeArguments.forEach { typeArgument ->
if(typeArgument is ConeTypeParameterTypeImpl) {
val param = buildTypeParameter {
symbol = FirTypeParameterSymbol()
containingDeclarationSymbol = this@buildSimpleFunction.symbol
origin = Key.origin
resolvePhase = FirResolvePhase.BODY_RESOLVE
moduleData = session.moduleData
this.name = typeArgument.lookupTag.name
variance = typeArgument.variance
typeArgument.collectUpperBounds(session.typeContext).mapTo(bounds) {
it.toFirResolvedTypeRef()
}
this.isReified = false
}
}
}
}
}HellRevenger
12/26/2025, 5:04 AMcopyFirFunctionWithResolvePhase(firFunction.fir, callableId, Key, FirResolvePhase.BODY_RESOLVE) {
receiverParameter = buildReceiverParameter {
symbol = FirReceiverParameterSymbol()
containingDeclarationSymbol = this@copyFirFunctionWithResolvePhase.symbol
origin = Key.origin
resolvePhase = FirResolvePhase.BODY_RESOLVE
typeRef = firFunction.dispatchReceiverType!!.toFirResolvedTypeRef()
moduleData = session.moduleData
}
dispatchReceiverType = null
}HellRevenger
12/26/2025, 5:08 AMCaleb B
12/26/2025, 5:09 AMCaleb B
12/26/2025, 5:10 AMHellRevenger
12/26/2025, 5:10 AMHellRevenger
12/29/2025, 12:57 AMval typeSymbols = typeParameters.associateBy { it.name }
fun rebuildConeType(coneType: ConeTypeProjection): ConeTypeProjection =
when (coneType) {
is ConeClassLikeTypeImpl -> {
ConeClassLikeTypeImpl(coneType.lookupTag, coneType.typeArguments.map {
rebuildConeType(it)
}.toTypedArray(), coneType.isMarkedNullable)
}
is ConeKotlinTypeConflictingProjection -> ConeKotlinTypeConflictingProjection(rebuildConeType(coneType.type) as ConeKotlinType)
is ConeKotlinTypeProjectionIn -> ConeKotlinTypeProjectionIn(rebuildConeType(coneType.type) as ConeKotlinType)
is ConeKotlinTypeProjectionOut -> ConeKotlinTypeProjectionOut(rebuildConeType(coneType.type) as ConeKotlinType)
is ConeStarProjection -> ConeStarProjection
is ConeTypeParameterTypeImpl -> {
ConeTypeParameterTypeImpl(ConeTypeParameterLookupTag(typeSymbols[coneType.lookupTag.name]!!.symbol), coneType.isMarkedNullable)
}
is ConeFlexibleType -> {
ConeFlexibleType(
rebuildConeType(coneType.lowerBound) as ConeRigidType,
rebuildConeType(coneType.upperBound) as ConeRigidType,
coneType.isTrivial
)
}
// I haven't meet these types
is ConeDefinitelyNotNullType -> TODO()
is ConeCapturedType -> TODO()
is ConeIntegerConstantOperatorType -> TODO()
is ConeIntegerLiteralConstantType -> TODO()
is ConeIntersectionType -> TODO()
is ConeStubTypeForTypeVariableInSubtyping -> TODO()
is ConeTypeVariableType -> TODO()
is ConeLookupTagBasedType -> TODO()
}
fun rebuildResolvedTypeRef(coneType: ConeTypeProjection) =
FirResolvedTypeRefBuilder().apply {
this.coneType = rebuildConeType(coneType) as ConeKotlinType
}.build()
for every TypeRef in receiver(typeRef)/value parameter(returnTypeRef)/return type(returnTypeRef), you need to call rebuildResolvedTypeRef to rebuild it