I want to create an extension function by just dir...
# compiler
h
I want to create an extension function by just directly call a dispatch function with the same arguments while the function is using type parameters. and i found that there're 2 way to add type parameters:
Copy code
// 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
Copy code
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?
✅ 1
c
The last image might be a normal issue; I get that all the time even when doing
return mutableListOf().apply { }
. Does it work when you specify the type param?
h
no it doesn't work
c
Welp, was worth checking at least. Have you tried stepping through the test compilation in the debugger, seeing what the state of the IR is when that error is thrown? And what does the
idea.log
file say when the error occurs in the IDE?
I don't know the answer off-hand, so if anyone knows what's going on, please feel free to take over. I'm just helping troubleshoot since I'm here.
h
there's no error in idea.log
c
If it's throwing the same exception, or any exception really, it normally appears in the log. Could you try triggering the error again and checking it to make sure it wasn't just drowned out by other stuff? I'd also strongly recommend debug-running a box test with just that code and setting a breakpoint where the error happens, so you can see the state of the FIR when it occurs.
h
do I need to modify the
simple.fir.txt
or i can generate it from where
c
If you're writing a new test kt file, a
.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.
I'd recommend temporarily deleting the template test files, or replacing them with
<name>.kt.bak
, so the breakpoint doesn't trigger for anything except the one thing you're trying to inspect.
Wait no, you can run individual tests. Run the
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.
You can also use the debugger to inspect how normal Kotlin code is compiled through the same means: write a file you know will work, set breakpoints on places you know will be called (like the constructor for a given FIR symbol), and you'll be able to see how the compiler expects them to be structured.
h
both way result in
<Unresolved name: fakeGet>
in
.fir.txt
, and then it doesn't generate
fir.ir.txt
since
.fir.txt
is different from expected
c
then it doesn't generate
fir.ir.txt
since
.fir.txt
is different from expected
If you delete the
.fir.txt
file for the test, it'll be forced to generate a new one.
h
yeah
it generate
.fir.txt
but no
fir.ir.txt
c
Ah yeah
h
oh wait i forgot to add package to new test file. the correct error in
.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 way
c
You should set a breakpoint where the stacktrace says the error originated and then run the test in debug mode. That way you'll get a better look at what exactly is going on when the error occurs.
h
but there's no error when generating the FIR, the error is in the generated
.fir.txt
?
c
Also, just to clarify, is this what you're trying to do?
Copy code
class Foo<T> {
  // Given this function:
  fun someFunction()
}

// Generate this:
fun <T> Foo<T>.someFunction_fake() {
  this.someFunction<T>()
}
h
Copy code
class Foo<T> {
  // Given this function:
  fun someFunction(): T
}

// Generate this:
fun <T> Foo<T>.someFunction_fake(): T {
  return this.someFunction()
}
👍 1
same error might occur in value arguments too
c
When you do this:
Copy code
val param: FirTypeParameterSymbol = ... // get from original function
How are you getting the param?
Because that member function wouldn't have a type parameter, it would just be the owning class's param
Also, you might want to consider just invoking
copyFirFunctionWithResolvePhase
on its own, and then adding a receiver parameter and changing the sources
h
Copy code
val classSymbol = session.symbolProvider.getClassLikeSymbolByClassId(ClassId.fromString("java.util.List")) as? FirClassSymbol
    ?: return@let
classSymbol.typeParameterSymbols.forEach { typeParameterSymbol ->
    typeParameterSymbol // <-- here
}
also
Copy code
val 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
                }
            }
        }
    }
}
the result is the same with
Copy code
copyFirFunctionWithResolvePhase(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
}
wait not the same, it raised should not be called
c
I'm not familiar enough with it to immediately notice anything wrong with what you've shown, so I don't know if I can help further. It's also midnight over here, lol Until more knowledgeable people get on (in about 9 hours, give or take), try stepping through the normal compilation process for functions like this in the debugger and see if you can find other synthetic function generators in the compiler that do the same thing. I wouldn't be surprised if Lombok or Metro does something similar to this.
Sorry I couldn't help much, but someone should be on to help later
h
okay, thanks for help
for anyone who meet this problem, to resolve this you need to rebuild `ConeType`s to bound to the type parameter symbols you just created for the function
Copy code
val 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