MerlinTHS
03/06/2023, 3:03 PMFirExpressionResolutionExtension
. I mainly "misused" it for some code modifications becuase implicit bodies are resolved at this phase. When I try to build code for throwing an ( Java ) exception ( let's say java.lang.Exception ), it works as long as I only use the noarg constructor.
context (FirSession)
fun buildExceptionThrow(): FirThrowExpression = buildThrowExpression {
val exceptionId = ClassId.fromString("java/lang/Exception")
val constructor = exceptionId.noArgConstructor
exception = buildFunctionCall {
typeRef = buildResolvedTypeRef {
type = exceptionId.createConeType(this@FirSession)
}
calleeReference = buildResolvedNamedReference {
name = constructor.name
resolvedSymbol = constructor
}
}
}
context (FirSession)
val ClassId.noArgConstructor: FirConstructorSymbol
get() = symbolProvider
.getClassDeclaredConstructors(this)
.firstOrNull {
it.valueParameterSymbols.isEmpty()
} ?: throw IllegalStateException("Noarg constructor not found!")
Now I want to use the message constructor ( org.jetbrains.kotlin.fir.java.declarations.FirJavaConstructor@68cb8e52: public constructor(p0: java.lang.String): R|java/lang/Exception| ).
@OptIn(SymbolInternals::class)
val FirConstructorSymbol.takesSingleString: Boolean
get() = with(valueParameterSymbols) {
(size == 1) && first().fir.returnTypeRef.coneType.isString
}
Filtering the parameters is kind of tricky because simply calling the first().resolvedReturnType.isString
on an FirJavaTypeRef
would immediately result in an ClassCastException
( java.lang.ClassCastException: class org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef cannot be cast to class org.jetbrains.kotlin.fir.types.FirResolvedTypeRef (org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef and org.jetbrains.kotlin.fir.types.FirResolvedTypeRef are in unnamed module of loader 'app'
). Maybe someone knows a better way to check the types than having to access symbol internals.
context (FirSession)
fun buildExceptionThrow(
message: String
): FirThrowExpression = buildThrowExpression {
val exceptionId = ClassId.fromString("java/lang/Exception")
val constructor = exceptionId.singleStringConstructor
exception = buildFunctionCall {
typeRef = buildResolvedTypeRef {
type = exceptionId.createConeType(this@FirSession)
}
calleeReference = buildResolvedNamedReference {
name = constructor.name
resolvedSymbol = constructor
}
argumentList = buildArgumentList {
arguments.add(message asResolved ConstantValueKind.String)
}
}
}
context (FirSession)
infix fun <Type> Type.asResolved(
kind: ConstantValueKind<Type>
): FirConstExpression<Type> =
buildConstExpression(null, kind, value = this).apply {
replaceTypeRef(
buildResolvedTypeRef {
type = kind.expectedConeType(this@FirSession)
}
)
}
context (FirSession)
val ClassId.singleStringConstructor: FirConstructorSymbol
get() = symbolProvider
.getClassDeclaredConstructors(this)
.firstOrNull(FirConstructorSymbol::takesSingleString)
?: throw IllegalStateException("No constructor with single String argument found!")
When I pass the argument in the FirArgumentListBuilder
as an FirConstExpression
, I get java.lang.IllegalStateException: Expected FirResolvedTypeRef with ConeKotlinType but was FirJavaTypeRef java.lang.String
. I already tried to replace the typeRef with a FirJavaTypeRef
, but to build one using buildJavaTypeRef
I need an instance of JavaType
- and I'm not sure how to get that. Does someone have experience with calling Java functions ( in this case a constructor ) in FIR?dmitriy.novozhilov
03/06/2023, 3:15 PMsymbolProvider.getClassDeclaredConstructors(this)
)
Instead of it you should use scopes (symbol.unsubstitutedScope(session)
MerlinTHS
03/06/2023, 10:55 PMcontext (FirSession)
val ClassId.singleStringConstructor: FirConstructorSymbol get() =
when(val symbol = symbolProvider.getClassLikeSymbolByClassId(this)) {
is FirClassSymbol -> symbol.findConstructor { takesSingleString }
else -> null
} ?: throw IllegalStateException("No constructor with single String argument found!")
context (FirSession)
fun FirClassSymbol<*>.findConstructor(
predicate: FirConstructorSymbol.() -> Boolean
): FirConstructorSymbol? =
unsubstitutedScope(this@FirSession, ScopeSession(), true)
.getDeclaredConstructors()
.firstOrNull(predicate)
Now the types changed. Instead of org.jetbrains.kotlin.fir.java.declarations.FirJavaConstructor@4a36a35d: public constructor(p0: java.lang.String): R|java/lang/Exception|
it's now org.jetbrains.kotlin.fir.declarations.impl.FirConstructorImpl@4f1f2f84: public constructor(p0: R|kotlin/String!|): R|java/lang/Exception|
. Since it's a Java method, the constructor takes a platform-type String. To make it work for this simple scenario I just checked the rendered text representation.
val FirConstructorSymbol.takesSingleString: Boolean
get() = with(valueParameterSymbols) {
(size == 1) && first().resolvedReturnType.renderReadable() == "String!"
}
Are there any utility methods to check platform types? ( Something like FirTypeRef.isBuiltinType(classId: ClassId, isNullable: Boolean) from FirTypeUtils.kt )dmitriy.novozhilov
03/07/2023, 9:56 AMunsubstitutedScope(this@FirSession, ScopeSession(), true).getDeclaredConstructors()
Actually, for constructors it's enough to use declared scope, it does not requre scope session
Something likeFirTypeRef.isBuiltinType(classId: ClassId, isNullable: Boolean)
FirTypeRef
represents some place in tree which have type, not type itself
If you want to use types you need to extract ConeKotlinType
from it (typeRef.coneType
or typeRef.coneTypeSafe<ConeKotlinType>()
if this particular type ref may be not resolved at moment you ask for it)
Then you can use various number of utilities which exist for cone types
If you want to check if this type with type constuctor with specific id then it's enough to check coneType.classId == classId
Also if you work with potentially platform (flexible) types, this may be useful:
• coneType.lowerIfFlexible()
unwraps flexible type to lower bound (String! = (String..String?)
to String
)
• coneType is ConeFlexibleType
to check if type is really flexibleMerlinTHS
03/07/2023, 12:18 PMlowerIfFlexible()
I found in org/jetbrains/kotlin/types/flexibleTypes.kt extends only KotlinType
( which isn't a supertype of ConeKotlinType
). I ended up with using lowerBoundIfFlexible()
from org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt which extends ConeKotlinType
.dmitriy.novozhilov
03/07/2023, 12:48 PMMerlinTHS
03/07/2023, 1:20 PMdeclaredMemberScope()
extension for FirClassSymbols
but it seems to be far away from what I'm looking for ( And would again cause a ClassCastException
because the class symbol for java.lang.Exception
originally contains a Java parameter reference of type FirJavaTypeRef
).dmitriy.novozhilov
03/07/2023, 1:23 PMMerlinTHS
03/07/2023, 1:33 PMScopeSession
like
unsubstitutedScope(this@FirSession, ScopeSession(), true)
.getDeclaredConstructors()
is currently the best solution for working with Java classes.