[fleet] Move rpc compiler plugin to community/fleet/compiler-plugins/rpc/ (IJI-2964)

GitOrigin-RevId: b8d1d86438f4a1550e02d9cc60b048d5017e10d1
This commit is contained in:
Ilia Kirianovskii
2025-08-10 20:33:15 +00:00
committed by intellij-monorepo-bot
parent 3757b562c2
commit e5276e975c
22 changed files with 1936 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
out/
build/
.idea/
.gradle/
@@ -0,0 +1,89 @@
plugins {
kotlin("jvm")
`maven-publish`
`java-gradle-plugin`
}
val pluginVersion = run {
val pluginVersionOverride = System.getProperty("fleet.plugin.version.override")
if (pluginVersionOverride != null) {
return@run pluginVersionOverride
}
val versionRegex = Regex("const val VERSION = \"(.+)\"")
project.file("src/main/kotlin/com/jetbrains/fleet/rpc/plugin/gradle/RpcGradlePlugin.kt").readLines().firstNotNullOfOrNull { line ->
versionRegex.find(line)?.let { match ->
match.groupValues[1]
}
} ?: error("Cannot find `const val VERSION = \"...\"` in RpcGradlePlugin.kt")
}
// the compiler plugin will be used together with this Kotlin compiler
val KOTLIN_VERSION = "2.2.0"
// the compiler plugin will be built with these Kotlin LV/APIV
val KOTLIN_LANGUAGE_VERSION = "2.1"
val KOTLIN_API_VERSION = "2.1"
group = "com.jetbrains.fleet"
version = pluginVersion
repositories {
mavenCentral()
if ("SNAPSHOT" in KOTLIN_VERSION || "dev" in KOTLIN_VERSION) {
mavenLocal()
}
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:$KOTLIN_VERSION")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api:$KOTLIN_VERSION")
implementation("org.jetbrains:annotations:24.1.0")
}
java {
withSourcesJar()
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.fromVersion(KOTLIN_LANGUAGE_VERSION))
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.fromVersion(KOTLIN_API_VERSION))
// FREE COMPILER ARGS PLACEHOLDER PLEASE DO NOT CHANGE IT BEFORE DISCUSS IN #ij-monorepo-kotlin
}
}
gradlePlugin {
plugins {
create("rpcGradle") {
id = "rpc"
implementationClass = "com.jetbrains.fleet.rpc.plugin.gradle.RpcGradlePlugin"
displayName = "RPC compiler plugin"
description = "Use this plugin for modules, which use RPC"
}
}
}
publishing {
repositories {
maven {
url = uri("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies")
credentials {
username = prop("publishingUsername")
password = prop("publishingPassword")
}
}
}
publications {
create<MavenPublication>("rpcPlugin") {
groupId = "com.jetbrains.fleet"
artifactId = "rpc-compiler-plugin"
version = pluginVersion
from(components["java"])
}
}
}
fun prop(name: String): String? =
extra.properties[name] as? String
Binary file not shown.
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -0,0 +1,26 @@
rootProject.name = "rpc-compiler-plugin"
pluginManagement {
// the compiler plugin will be built by this Kotlin compiler
val KOTLIN_VERSION = "2.2.0"
plugins {
kotlin("jvm") version KOTLIN_VERSION
}
resolutionStrategy {
eachPlugin {
repositories {
gradlePluginPortal()
if ("SNAPSHOT" in KOTLIN_VERSION || "dev" in KOTLIN_VERSION) {
mavenLocal()
}
}
}
}
repositories {
gradlePluginPortal()
if ("SNAPSHOT" in KOTLIN_VERSION || "dev" in KOTLIN_VERSION) {
mavenLocal()
}
}
}
@@ -0,0 +1,27 @@
package com.jetbrains.fleet.rpc.plugin
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.jetbrains.kotlin.config.CompilerConfiguration
import com.jetbrains.fleet.rpc.plugin.ir.ServiceGenerationExtension
import org.jetbrains.kotlin.config.CommonConfigurationKeys
@OptIn(ExperimentalCompilerApi::class)
class RpcCommandLineProcessor : CommandLineProcessor {
override val pluginId: String = "rpc"
override val pluginOptions: Collection<AbstractCliOption> = emptyList()
}
@OptIn(ExperimentalCompilerApi::class)
class RpcComponentRegistrar : CompilerPluginRegistrar() {
override val supportsK2: Boolean = true
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
val messageCollector = configuration.get(CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
IrGenerationExtension.registerExtension(ServiceGenerationExtension(messageCollector))
}
}
@@ -0,0 +1,25 @@
package com.jetbrains.fleet.rpc.plugin.gradle
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
private const val VERSION = "2.2.0-0.2"
class RpcGradlePlugin : KotlinCompilerPluginSupportPlugin {
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true
override fun getCompilerPluginId(): String = "rpc"
override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
groupId = "com.jetbrains.fleet",
artifactId = "rpc-compiler-plugin",
version = VERSION
)
override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
return kotlinCompilation.target.project.provider { emptyList() }
}
}
@@ -0,0 +1,255 @@
package com.jetbrains.fleet.rpc.plugin.ir
import com.jetbrains.fleet.rpc.plugin.ir.util.*
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.jvm.codegen.AnnotationCodegen.Companion.annotationClass
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.builders.declarations.addDefaultGetter
import org.jetbrains.kotlin.ir.builders.declarations.addProperty
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.fromSymbolOwner
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
private val flowPackage = FqName.fromSegments(listOf("kotlinx", "coroutines", "flow"))
private val flowClassId = ClassId(packageFqName = flowPackage, topLevelName = "Flow".name)
private val flowCollectorClassId = ClassId(packageFqName = flowPackage, topLevelName = "FlowCollector".name)
private val flowFnId = CallableId(packageName = flowPackage, callableName = "flow".name)
private val flowCollectFnId = CallableId(classId = flowClassId, callableName = "collect".name)
private val asyncPackage = FqName.fromSegments(listOf("fleet", "util", "async"))
private val resourceFnId = CallableId(packageName = asyncPackage, callableName = "resource".name)
private val resourceUseFnId = CallableId(classId = ClassId(asyncPackage, "Resource".name), callableName = "use".name)
private val consumedClassId = ClassId(asyncPackage, "Consumed".name)
private val coroutineScopeClassId = ClassId(FqName.fromSegments(listOf("kotlinx", "coroutines")), "CoroutineScope".name)
fun buildClientStub(interfaceClass: IrClass, rpcMethods: List<IrSimpleFunction>, context: CompilerPluginContext): IrClass {
val pluginContext = context.pluginContext
// Resolve necessary refs
val flowCollectorClass = pluginContext.referenceClass(flowCollectorClassId)!!
val flowFunction = pluginContext.referenceFunctions(flowFnId).first()
val collectFunction = pluginContext.referenceFunctions(flowCollectFnId).first()
val resourceFunction = pluginContext.referenceFunctions(resourceFnId).first()
val useFunction = pluginContext.referenceFunctions(resourceUseFnId).first()
val consumed = pluginContext.referenceClass(consumedClassId)!!
val coroutineScope = pluginContext.referenceClass(coroutineScopeClassId)!!
val generatedClass = pluginContext.buildClassBase {
name = Name.identifier(interfaceClass.name.identifier + "ClientStub")
visibility = interfaceClass.visibility
}.also {
it.parent = interfaceClass.parent
it.superTypes = listOfNotNull(interfaceClass.defaultType)
it.annotations += interfaceClass.extractApiStatusAnnotations()
val container = interfaceClass.parent as? IrClass ?: interfaceClass.file
container.addChild(it)
}
val invocationHandler = generatedClass.addInvocationHandlerToPrimaryConstructor(pluginContext)
for (rpcMethod in rpcMethods) {
val originalFunction = rpcMethod.symbol.owner
pluginContext.addFunctionOverride(originalFunction, generatedClass) { generatedFunction ->
generatedFunction.annotations += originalFunction.extractApiStatusAnnotations()
generatedFunction.generateBody(context) {
val irThis = irGet(generatedFunction.parameters[0])
// Obtain the 'invoke' function from the 'invocationHandler'
val invokeFunction = invocationHandler.backingField!!.type.getClass()!!.functions.first { it.name.asString() == "invoke" }
val invocationHandlerReceiver = irGetValue(invocationHandler, irThis) // this.invocationHandler
val invocationHandlerCall = irCall(invokeFunction.symbol).also { call -> // this.invocationHandler(methodName, args)
call.arguments[0] = invocationHandlerReceiver
call.arguments[1] = irString(originalFunction.name.identifier) // methodName
val args = createArrayOfExpression(
pluginContext.irBuiltIns.anyNType,
// All parameters except the first (dispatch receiver)
generatedFunction.parameters.drop(1).map { irGet(it) },
pluginContext
)
call.arguments[2] = args // args
}
when {
originalFunction.isNonSuspendFlowFunction() -> {
val flowElementType = when (val arg = (originalFunction.returnType as IrSimpleType).arguments.first()) {
is IrStarProjection -> pluginContext.irBuiltIns.anyNType
is IrTypeProjection -> arg.type
}
// flow { it -> invocationHandler(...).collect(it) }
val wrappedCall = irCall(flowFunction).also { flowCall ->
val flowLambda = pluginContext.irLambda(
originalFunction,
parent = generatedFunction,
pluginContext.irBuiltIns.suspendFunctionN(1).typeWith(
flowCollectorClass.typeWith(flowElementType),
pluginContext.irBuiltIns.unitType
)
) { flowLambda ->
flowLambda.generateBody(context) {
+irReturn(
irCall(collectFunction).also { collectCall ->
collectCall.arguments[0] = invocationHandlerCall
// We're getting a flow collector in the lambda, just gotta pass it
collectCall.arguments[1] = irGet(flowLambda.parameters.first())
}
)
}
}
flowCall.arguments[0] = flowLambda
}
+irReturn(wrappedCall)
}
originalFunction.isNonSuspendResourceFunction() -> {
val resourceElementType =
((originalFunction.returnType as IrSimpleType)
.arguments
.first()
as IrTypeProjection
).type
/*
* resource { consume -> remoteCall().use { it -> consume(it) } }
*/
val wrappedCall = irCall(resourceFunction).also { flowCall ->
val consumerType = pluginContext.irBuiltIns.suspendFunctionN(1).typeWith(
resourceElementType,
consumed.defaultType,
)
val resourceLambda = pluginContext.irLambda(
originalFunction,
parent = generatedFunction,
// suspend CoroutineScope.(consumer: Consumer<T>) -> Consumed
pluginContext.irBuiltIns.suspendFunctionN(2).typeWith(
coroutineScope.defaultType,
consumerType,
consumed.defaultType
)
) { resourceLambda ->
resourceLambda.generateBody(context) {
+irReturn(
// use { it -> consume(it) }
irCall(useFunction).also { useCall ->
useCall.arguments[0] = invocationHandlerCall
useCall.arguments[1] = pluginContext.irLambda(
originalFunction,
parent = resourceLambda,
// suspend CoroutineScope.(T) -> U
pluginContext.irBuiltIns.suspendFunctionN(2).typeWith(
coroutineScope.defaultType,
resourceElementType,
consumed.defaultType,
)
) { useLambda ->
useLambda.generateBody(context) {
+irReturn(irCall(consumerType.classOrFail.getSimpleFunction("invoke")!!).also { collectCall ->
// Call the resource consumer (second parameter: coroutine scope is first)
collectCall.arguments[0] = irGet(resourceLambda.parameters[1])
// ... with our result
collectCall.arguments[1] = irGet(useLambda.parameters[1])
})
}
}
}
)
}
}
flowCall.arguments[0] = resourceLambda
}
+irReturn(wrappedCall)
}
else -> {
+irReturn(invocationHandlerCall)
}
}
}
}
}
return generatedClass
}
private fun irGetValue(irProperty: IrProperty, receiver: IrExpression?): IrCall =
IrCallImpl.fromSymbolOwner(
startOffset = SYNTHETIC_OFFSET,
endOffset = SYNTHETIC_OFFSET,
type = irProperty.backingField!!.type,
symbol = irProperty.getter!!.symbol,
origin = IrStatementOrigin.GET_PROPERTY,
).apply {
dispatchReceiver = receiver
}
private fun IrClass.addInvocationHandlerToPrimaryConstructor(pluginContext: IrPluginContext): IrProperty {
val primaryConstructor = this.primaryConstructor!!
val invocationHandlerType = pluginContext.getProxyType()
val invocationHandler = primaryConstructor.addValueParameter("invocationHandler".name, invocationHandlerType)
return addIrValProperty(
valName = invocationHandler.name,
valType = invocationHandlerType,
valInitializer = irGet(invocationHandler),
pluginContext = pluginContext,
)
}
internal fun IrAnnotationContainer.extractApiStatusAnnotations(): List<IrConstructorCall> {
return annotations.filter { it.annotationClass.kotlinFqName.startsWith(FqName.fromSegments(listOf("org", "jetbrains", "annotations", "ApiStatus"))) }
}
private fun IrClass.addIrValProperty(
valName: Name,
valType: IrType,
valInitializer: IrExpression,
pluginContext: IrPluginContext,
): IrProperty {
val irClass = this
val irField = pluginContext.irFactory.buildField {
name = valName
type = valType
origin = IrDeclarationOrigin.PROPERTY_BACKING_FIELD
visibility = DescriptorVisibilities.PRIVATE
}.apply {
parent = irClass
initializer = pluginContext.irFactory.createExpressionBody(valInitializer)
}
val irProperty = irClass.addProperty {
name = valName
isVar = false
}.apply {
parent = irClass
backingField = irField
addDefaultGetter(irClass, pluginContext.irBuiltIns)
}
return irProperty
}
@@ -0,0 +1,39 @@
package com.jetbrains.fleet.rpc.plugin.ir
import com.jetbrains.fleet.rpc.plugin.ir.util.collectRpcMethods
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.util.isInterface
import com.jetbrains.fleet.rpc.plugin.ir.util.hasRpcAnnotation
import com.jetbrains.fleet.rpc.plugin.ir.util.isNonSuspendFlowFunction
import com.jetbrains.fleet.rpc.plugin.ir.util.isNonSuspendResourceFunction
import org.jetbrains.kotlin.backend.common.getCompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.ir.util.file
class GenerateClasses(private val context: CompilerPluginContext) : IrElementTransformerVoidWithContext() {
override fun visitClassNew(declaration: IrClass): IrStatement {
if (declaration.isInterface && declaration.hasRpcAnnotation()) {
val rpcMethods = collectRpcMethods(declaration).filter {
if (it.isSuspend || it.isNonSuspendFlowFunction() || it.isNonSuspendResourceFunction()) {
true
}
else {
// Forbid unsupported methods
context.messageCollector.report(
CompilerMessageSeverity.ERROR,
"${declaration.name} should either be suspend, return Resource<out RemoteResource> or return Flow<*> to be supported in @Rpc interface",
it.getCompilerMessageLocation(declaration.file)
)
false
}
}
val clientStubClass = buildClientStub(declaration, rpcMethods, context)
buildRemoteApiDescriptorImpl(declaration, clientStubClass, rpcMethods, context)
}
return super.visitClassNew(declaration)
}
}
@@ -0,0 +1,294 @@
package com.jetbrains.fleet.rpc.plugin.ir
import com.jetbrains.fleet.rpc.plugin.ir.remoteKind.REMOTE_KIND_FQN
import com.jetbrains.fleet.rpc.plugin.ir.remoteKind.toRemoteKind
import com.jetbrains.fleet.rpc.plugin.ir.util.*
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildValueParameter
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrParameterKind
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import kotlin.collections.plus
fun buildRemoteApiDescriptorImpl(
interfaceClass: IrClass,
clientStub: IrClass,
rpcMethods: List<IrSimpleFunction>,
context: CompilerPluginContext
): IrClass {
val generatedClass = context.pluginContext.buildClassBase {
name = interfaceClass.name.getRemoteApiDescriptorImplName()
visibility = interfaceClass.visibility
kind = ClassKind.OBJECT
}.also {
it.parent = interfaceClass.parent
val newTypeArgument = IrSimpleTypeImpl(
classifier = interfaceClass.symbol,
hasQuestionMark = false,
arguments = emptyList(),
annotations = emptyList()
)
val parametrizedClientType: IrSimpleType = IrSimpleTypeImpl(
classifier = context.remoteApiDescriptorClass.symbol,
hasQuestionMark = false,
arguments = listOf(newTypeArgument),
annotations = emptyList()
)
it.annotations += interfaceClass.extractApiStatusAnnotations()
it.superTypes = listOf(parametrizedClientType)
val container = interfaceClass.parent as? IrClass ?: interfaceClass.file
container.addChild(it)
}
overrideGetSignatureFunction(rpcMethods, generatedClass, interfaceClass, context)
overrideCallFunction(rpcMethods, generatedClass, interfaceClass, context)
overrideClientStubFunction(generatedClass, interfaceClass, clientStub, context)
overrideGetApiFqnFunction(generatedClass, interfaceClass, context)
return generatedClass
}
private fun overrideGetSignatureFunction(rpcMethods: List<IrSimpleFunction>,
generatedClass: IrClass,
interfaceClass: IrClass,
context: CompilerPluginContext) { // fun getSignature(methodName: String): RpcSignature
overrideFunctionWithName("getSignature", generatedClass, context) { function ->
function.generateBody(context) {
val methodName = irTemporary(irGet(function.parameters[1]))
whenBlockForMethods(methodName, rpcMethods, interfaceClass, context) { remoteApiFunction ->
val errorMessage = "${interfaceClass.kotlinFqName}/${remoteApiFunction.name}"
val parameters = createArrayOfExpression(
arrayElementType = context.parameterDescriptorClass.defaultType,
arrayElements = remoteApiFunction.parameters.filter { it.kind == IrParameterKind.Regular }.map { parameter ->
irCallConstructor(
callee = context.parameterDescriptorConstructor,
typeArguments = listOf(context.pluginContext.irBuiltIns.stringType, context.remoteKindClass.defaultType)
).apply {
arguments[0] = irString(parameter.name.identifier) // parameterName
arguments[1] = toRemoteKind(parameter.type, context, errorMessage) // parameterKind
}
},
pluginContext = context.pluginContext
)
val rpcSignature = irCallConstructor(
callee = context.rpcSignatureConstructor,
typeArguments = listOf(context.pluginContext.irBuiltIns.stringType, parameters.type, context.remoteKindClass.defaultType)
).apply {
arguments[0] = irString(remoteApiFunction.name.identifier) // methodName
arguments[1] = parameters // parameters
arguments[2] = toRemoteKind(remoteApiFunction.returnType, context, errorMessage) // returnType
}
irReturn(rpcSignature)
}
}
}
}
private val CompilerPluginContext.rpcSignatureConstructor
get() = cache.remember {
val rpcSignatureClass = pluginContext.referenceClass(ClassId.topLevel(RPC_FQN.child("RpcSignature".name)))!!
rpcSignatureClass.constructors.single()
}
private val CompilerPluginContext.parameterDescriptorClass
get() = cache.remember {
pluginContext.referenceClass(ClassId.topLevel(RPC_FQN.child("ParameterDescriptor".name)))!!
}
private val CompilerPluginContext.parameterDescriptorConstructor
get() = cache.remember {
parameterDescriptorClass.constructors.single()
}
private val CompilerPluginContext.remoteKindClass
get() = cache.remember {
pluginContext.referenceClass(ClassId.topLevel(REMOTE_KIND_FQN))!!
}
private fun overrideClientStubFunction(generatedClass: IrClass,
interfaceClass: IrClass,
clientStub: IrClass,
context: CompilerPluginContext) { // fun clientStub(proxy: Proxy): T
overrideFunctionWithName("clientStub", generatedClass, context) { function ->
function.returnType = interfaceClass.defaultType
function.generateBody(context) {
val clientStubConstructor = clientStub.primaryConstructor?.symbol!!
val proxyType = context.pluginContext.getProxyType()
val clientStubInstance = irCallConstructor(clientStubConstructor, listOf(proxyType)).apply {
arguments[0] = irGet(function.parameters[1])
}
+irReturn(clientStubInstance)
}
}
}
private fun overrideGetApiFqnFunction(generatedClass: IrClass,
interfaceClass: IrClass,
context: CompilerPluginContext) { // fun getApiFqn(): String
overrideFunctionWithName("getApiFqn", generatedClass, context) { function ->
function.generateBody(context) {
+irReturn(irString(interfaceClass.kotlinFqName.asString()))
}
}
}
private fun overrideCallFunction(rpcMethods: List<IrSimpleFunction>,
generatedClass: IrClass,
interfaceClass: IrClass,
context: CompilerPluginContext) { // suspend fun call(impl: T, methodName: String, args: Array<Any?>): Any?
val oldFunction = checkNotNull(context.remoteApiDescriptorClass.getSimpleFunction("call")).owner
context.pluginContext.addFunctionOverride(oldFunction, generatedClass) { function ->
// change first regular parameter type from T to actual interface type (interfaceClass.defaultType)
function.parameters = function.parameters.mapIndexed { index, parameter ->
if (index == 1) {
buildValueParameter(function) {
name = parameter.name
updateFrom(parameter)
type = interfaceClass.defaultType
}
} else {
parameter
}
}
function.generateBody(context) {
val methodName = irTemporary(irGet(function.parameters[2]))
whenBlockForMethods(methodName, rpcMethods, interfaceClass, context) { remoteApiFunction ->
val call = callRemoteApiFunction(remoteApiFunction, irGet(function.parameters[1]), irGet(function.parameters[3]), context.pluginContext)
irReturn(call)
}
}
}
}
private fun overrideFunctionWithName(name: String,
generatedClass: IrClass,
context: CompilerPluginContext,
block: (IrSimpleFunction) -> Unit) {
val function = checkNotNull(context.remoteApiDescriptorClass.getSimpleFunction(name)).owner
context.pluginContext.addFunctionOverride(function, generatedClass, block)
}
private fun IrBlockBodyBuilder.whenBlockForMethods(methodName: IrVariable,
rpcMethods: List<IrSimpleFunction>,
interfaceClass: IrClass,
context: CompilerPluginContext,
block: IrBlockBodyBuilder.(IrSimpleFunction) -> IrExpression) {
+irBlock(
origin = IrStatementOrigin.WHEN
) {
+irWhen(
context.pluginContext.irBuiltIns.unitType,
rpcMethods.map { whenBranch(it, methodName, context, block) } + irElseBranch(
generateErrorFunctionCall(
irString("${interfaceClass.kotlinFqName} does not have method ").plusString(irGet(methodName), context),
context
)
)
)
}
}
private fun IrConst.plusString(otherString: IrExpression, context: CompilerPluginContext): IrExpression {
return irCall(stringPlusFunction(context)).apply {
arguments[0] = this@plusString
arguments[1] = otherString
}
}
private fun stringPlusFunction(context: CompilerPluginContext) = context.cache.remember {
context.pluginContext.referenceFunctions(CallableId(
classId = ClassId(FqName.fromSegments(listOf("kotlin")), "String".name),
callableName = "plus".name
)).first().owner.symbol
}
private fun generateErrorFunctionCall(argument: IrExpression, context: CompilerPluginContext): IrExpression {
return irCall(errorFunction(context)).apply {
arguments[0] = argument
}
}
private fun errorFunction(context: CompilerPluginContext) = context.cache.remember {
context.pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlin")),
className = null,
callableName = "error".name
)).first().owner.symbol
}
private fun IrBlockBodyBuilder.whenBranch(
remoteApiFunction: IrSimpleFunction,
methodName: IrVariable,
context: CompilerPluginContext,
block: IrBlockBodyBuilder.(IrSimpleFunction) -> IrExpression,
): IrBranch =
irBranch(
irEquals(
irGet(methodName),
context.pluginContext.irBuiltIns.irString(remoteApiFunction.name.identifier),
IrStatementOrigin.EQEQ
),
block(remoteApiFunction)
)
private fun IrBuiltIns.irString(value: String): IrConst = IrConstImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
stringType,
IrConstKind.String,
value
)
private fun IrBlockBodyBuilder.callRemoteApiFunction(remoteApiFunction: IrSimpleFunction,
dispatchReceiverExpression: IrExpression,
argsExpression: IrExpression,
pluginContext: IrPluginContext): IrExpression {
val getFunction = pluginContext.irBuiltIns.arrayClass.owner.declarations
.filterIsInstance<IrSimpleFunction>()
.single { it.name.asString() == "get" }
return irCall(
remoteApiFunction.symbol,
).also { call ->
call.arguments[0] = dispatchReceiverExpression
// Parameters without dispatcher
val valueParameters = remoteApiFunction.parameters.drop(1)
val args = irTemporary(argsExpression) // vararg with args
for (index in valueParameters.indices) {
val argument = irCall(getFunction.symbol).also { getCall ->
getCall.arguments[0] = irGet(args)
getCall.arguments[1] = irInt(index)
}
call.arguments[index + 1] = irCastIfNeeded(argument, valueParameters[index].type)
}
}
}
private val REMOTE_API_DESCRIPTOR_INTERFACE_FQN = RPC_FQN.child("RemoteApiDescriptor".name)
private val CompilerPluginContext.remoteApiDescriptorClass
get() = cache.remember {
pluginContext.referenceClass(ClassId.topLevel(REMOTE_API_DESCRIPTOR_INTERFACE_FQN))?.owner!!
}
@@ -0,0 +1,54 @@
package com.jetbrains.fleet.rpc.plugin.ir
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import com.jetbrains.fleet.rpc.plugin.ir.util.RPC_FQN
import com.jetbrains.fleet.rpc.plugin.ir.util.name
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
import org.jetbrains.kotlin.ir.types.classOrNull
class RemoteApiDescriptorTransform : IrElementTransformerVoidWithContext() {
override fun visitCall(expression: IrCall): IrExpression {
if (expression.symbol.owner.kotlinFqName == REMOTE_API_DESCRIPTOR_FUNCTION_FQN) {
val descriptorInstance = getRemoteApiDescriptorInstance(checkNotNull(expression.typeArguments[0])) {
currentDeclarationParent?.dumpKotlinLike()
}
expression.arguments[0] = descriptorInstance
}
return super.visitCall(expression)
}
}
fun getRemoteApiDescriptorInstance(type: IrType, debugInfo: () -> String?): IrExpression {
val irClass = requireNotNull(type.classOrNull) {
"Error in RPC compiler plugin: can't get class by type ${type.dumpKotlinLike()}, error occured in\n${debugInfo()}"
}.owner
val implClass =
IrFactoryImpl.buildClass {
name = irClass.name.getRemoteApiDescriptorImplName()
kind = ClassKind.OBJECT
}.apply {
parent = irClass.parent
createThisReceiverParameter()
}
return IrGetObjectValueImpl(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
type = implClass.defaultType,
symbol = implClass.symbol
)
}
fun Name.getRemoteApiDescriptorImplName() = Name.identifier(identifier + "RemoteApiDescriptorImpl")
val REMOTE_API_DESCRIPTOR_FUNCTION_FQN = RPC_FQN.child("remoteApiDescriptor".name)
@@ -0,0 +1,20 @@
package com.jetbrains.fleet.rpc.plugin.ir
import com.jetbrains.fleet.rpc.plugin.ir.util.Cache
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
class ServiceGenerationExtension(private val messageCollector: MessageCollector) : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val context = CompilerPluginContext(moduleFragment, pluginContext, messageCollector, Cache())
moduleFragment.accept(GenerateClasses(context), null)
moduleFragment.accept(RemoteApiDescriptorTransform(), null)
}
}
data class CompilerPluginContext(val moduleFragment: IrModuleFragment,
val pluginContext: IrPluginContext,
val messageCollector: MessageCollector,
val cache: Cache)
@@ -0,0 +1,165 @@
package com.jetbrains.fleet.rpc.plugin.ir.remoteKind
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.FqName
import com.jetbrains.fleet.rpc.plugin.ir.CompilerPluginContext
import com.jetbrains.fleet.rpc.plugin.ir.util.name
internal fun CompilerPluginContext.getSpecialSerializer(fqName: FqName?) = cache.remember {
val listSerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "ListSerializer".name
)).first()
val setSerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "SetSerializer".name
)).first()
val mapSerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "MapSerializer".name
)).first()
val pairSerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "PairSerializer".name
)).first()
val mapEntrySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "MapEntrySerializer".name
)).first()
val tripleSerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "TripleSerializer".name
)).first()
val charArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "CharArraySerializer".name
)).first()
val byteArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "ByteArraySerializer".name
)).first()
val uByteArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "UByteArraySerializer".name
)).first()
val shortArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "ShortArraySerializer".name
)).first()
val uShortArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "UShortArraySerializer".name
)).first()
val intArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "IntArraySerializer".name
)).first()
val uIntArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "UIntArraySerializer".name
)).first()
val longArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "LongArraySerializer".name
)).first()
val uLongArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "ULongArraySerializer".name
)).first()
val floatArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "FloatArraySerializer".name
)).first()
val doubleArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "DoubleArraySerializer".name
)).first()
val booleanArraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "BooleanArraySerializer".name
)).first()
val arraySerializer = pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "ArraySerializer".name
)).first { it.owner.parameters.size == 1 }
mapOf(
LIST_FQN to listSerializer,
SET_FQN to setSerializer,
MAP_FQN to mapSerializer,
PAIR_FQN to pairSerializer,
MAP_ENTRY_FQN to mapEntrySerializer,
TRIPLE_FQN to tripleSerializer,
CHAR_ARRAY_FQN to charArraySerializer,
BYTE_ARRAY_FQN to byteArraySerializer,
U_BYTE_ARRAY_FQN to uByteArraySerializer,
SHORT_ARRAY_FQN to shortArraySerializer,
U_SHORT_ARRAY_FQN to uShortArraySerializer,
INT_ARRAY_FQN to intArraySerializer,
U_INT_ARRAY_FQN to uIntArraySerializer,
LONG_ARRAY_FQN to longArraySerializer,
U_LONG_ARRAY_FQN to uLongArraySerializer,
FLOAT_ARRAY_FQN to floatArraySerializer,
DOUBLE_ARRAY_FQN to doubleArraySerializer,
BOOLEAN_ARRAY_FQN to booleanArraySerializer,
ARRAY_FQN to arraySerializer,
)
}[fqName]
private val LIST_FQN = FqName.fromSegments(listOf("kotlin", "collections", "List"))
private val SET_FQN = FqName.fromSegments(listOf("kotlin", "collections", "Set"))
private val MAP_FQN = FqName.fromSegments(listOf("kotlin", "collections", "Map"))
private val PAIR_FQN = FqName.fromSegments(listOf("kotlin", "Pair"))
private val MAP_ENTRY_FQN = FqName.fromSegments(listOf("kotlin", "collections", "Map", "Entry"))
private val TRIPLE_FQN = FqName.fromSegments(listOf("kotlin", "Triple"))
private val CHAR_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "CharArray"))
private val BYTE_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "ByteArray"))
private val U_BYTE_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "UByteArray"))
private val SHORT_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "ShortArray"))
private val U_SHORT_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "UShortArray"))
private val INT_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "IntArray"))
private val U_INT_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "UIntArray"))
private val LONG_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "LongArray"))
private val U_LONG_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "ULongArray"))
private val FLOAT_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "FloatArray"))
private val DOUBLE_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "DoubleArray"))
private val BOOLEAN_ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "BooleanArray"))
private val ARRAY_FQN = FqName.fromSegments(listOf("kotlin", "Array"))
@@ -0,0 +1,222 @@
package com.jetbrains.fleet.rpc.plugin.ir.remoteKind
import com.jetbrains.fleet.rpc.plugin.ir.CompilerPluginContext
import com.jetbrains.fleet.rpc.plugin.ir.getRemoteApiDescriptorInstance
import com.jetbrains.fleet.rpc.plugin.ir.util.RPC_FQN
import com.jetbrains.fleet.rpc.plugin.ir.util.name
import org.jetbrains.kotlin.backend.wasm.ir2wasm.allSuperInterfaces
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrParameterKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.types.classOrFail
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.typeOrFail
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
fun IrBuilderWithScope.toRemoteKind(irType: IrType, context: CompilerPluginContext, debugInfo: String): IrExpression {
return handleSpecialTypes(irType, context, debugInfo)
?: handleRemoteObject(irType, context)
?: handleResource(irType, context)
?: run { // RemoteKind.Data
val serializer = generateSerializerCall(irType, context, debugInfo)
irCallConstructor(context.remoteKindDataConstructor, listOf(serializer.type)).apply {
arguments[0] = serializer
}
}
}
private fun IrBuilderWithScope.handleResource(irType: IrType, context: CompilerPluginContext): IrExpression? {
return if (irType.classFqName == RESOURCE_FQN) {
// Get the class of the type argument
val serviceType = ((irType as? IrSimpleType)?.arguments?.singleOrNull() as? IrTypeProjection)?.type
val irClass = serviceType?.classOrNull?.owner
if (irClass != null && irClass.allSuperInterfaces().any { it.kotlinFqName == REMOTE_RESOURCE_FQN }) {
val remoteApiDescriptor = getRemoteApiDescriptorInstance(serviceType) {
"Resource generation"
}
irCallConstructor(context.remoteKindResourceConstructor, listOf(remoteApiDescriptor.type)).apply {
arguments[0] = remoteApiDescriptor
}
}
else {
null
}
}
else {
null
}
}
private fun IrBuilderWithScope.handleRemoteObject(irType: IrType, context: CompilerPluginContext): IrExpression? {
val irClass = irType.classOrFail.owner
return if (irClass.allSuperInterfaces().any { it.kotlinFqName == REMOTE_OBJECT_FQN }) {
val remoteApiDescriptor = getRemoteApiDescriptorInstance(irType) {
"RemoteObject generation"
}
irCallConstructor(context.remoteKindRemoteObjectConstructor, listOf(remoteApiDescriptor.type)).apply {
arguments[0] = remoteApiDescriptor
}
}
else {
null
}
}
private fun IrBuilderWithScope.generateSerializerCall(irType: IrType,
context: CompilerPluginContext,
debugInfo: String,
isTypeArgument: Boolean = false): IrExpression {
val irClass = irType.classOrFail.owner
// fallback to the class itself, for example Unit serializer() is defined on Unit since it has no Companion
val companionOrClass = irClass.companionObject() ?: irClass
val serializerCall =
// check if the class is one of the special ones that don't have serializer on Companion (List/Set/etc)
context.getSpecialSerializer(irClass.kotlinFqName)?.let { irCall(it) }
?: findBuiltinSerializerExtensionFunctions(companionOrClass, context)
// check that class has @Serializable annotation before trying to find serializer() method on it
?: irClass.getAnnotation(FqName.fromSegments(listOf("kotlinx", "serialization", "Serializable")))?.let {
findSerializerAsMethod(companionOrClass)
}
val serializer = serializerCall?.apply {
val valueParameters = symbol.owner.parameters.filter { it.kind == IrParameterKind.Regular }
(irType as IrSimpleType).arguments.zip(valueParameters).forEachIndexed { index, (typeArgument, parameter) ->
arguments[parameter.indexInParameters] = generateSerializerCall(typeArgument.typeOrFail, context, debugInfo, isTypeArgument = true)
typeArguments[index] = typeArgument.typeOrFail
}
}
return if (serializer == null) {
val message = "Couldn't find serializer function on companion object for ${irClass.dumpKotlinLike()}\nAdditional info:\n$debugInfo"
if (isTypeArgument) {
context.messageCollector.report(
CompilerMessageSeverity.WARNING,
message
)
irCallConstructor(context.throwingSerializerConstructor, listOf(this.context.irBuiltIns.stringType)).apply {
val errorString = "ThrowingSerializer was used for serialization/deserialization. " +
"This could happen if there is a @Serializable class with non-serializable type argument in rpc interface.\n" +
"Additional info:\n$debugInfo"
arguments[0] = irString(errorString)
}
} else {
error(message)
}
} else if (irType.isNullable()) {
irCall(context.nullableSerializerProperty.getter!!).apply {
insertExtensionReceiver(serializer)
}
}
else {
serializer
}
}
// find .serializer() as an extension function
private fun IrBuilderWithScope.findBuiltinSerializerExtensionFunctions(
companionOrClass: IrClass,
context: CompilerPluginContext,
): IrFunctionAccessExpression? {
val serializerFunction =
context.pluginContext.referenceFunctions(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "serializer".name
)).singleOrNullOrThrow { fn ->
fn.owner.parameters.firstOrNull { it.kind == IrParameterKind.ExtensionReceiver }?.type == companionOrClass.defaultType
}
return serializerFunction?.let {
irCall(serializerFunction).apply {
insertExtensionReceiver(irGetObject(companionOrClass.symbol))
}
}
}
// find .serializer() as a method of irClass
private fun IrBuilderWithScope.findSerializerAsMethod(irClass: IrClass): IrFunctionAccessExpression? {
return irClass.functions.asIterable().singleOrNullOrThrow { function ->
function.name.identifierOrNullIfSpecial == "serializer" &&
function.parameters.singleOrNull { it.kind == IrParameterKind.Regular }?.isVararg != true
}?.let {
irCall(it).apply {
dispatchReceiver = irGetObject(irClass.symbol)
}
}
}
private val CompilerPluginContext.nullableSerializerProperty
get() = cache.remember {
pluginContext.referenceProperties(CallableId(
packageName = FqName.fromSegments(listOf("kotlinx", "serialization", "builtins")),
className = null,
callableName = "nullable".name
)).first().owner
}
private val CompilerPluginContext.remoteKindDataConstructor
get() = cache.remember {
pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "Data")), false)
)!!.constructors.first()
}
private val CompilerPluginContext.remoteKindRemoteObjectConstructor
get() = cache.remember {
pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "RemoteObject")), false)
)!!.constructors.first()
}
private val CompilerPluginContext.remoteKindResourceConstructor
get() = cache.remember {
pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "Resource")), false)
)!!.constructors.first()
}
private val CompilerPluginContext.throwingSerializerConstructor
get() = pluginContext.referenceClass(
ClassId.topLevel(RPC_FQN.child("core".name).child("ThrowingSerializer".name))
)!!.constructors.first()
/**
* Same idea as [kotlin.collections.singleOrNull] but will throw if the collection contains more than one element.
* */
private inline fun <T> Iterable<T>.singleOrNullOrThrow(p: (T) -> Boolean = { true }): T? {
var single: T? = null
var found = false
for (element in this) {
if (p(element)) {
if (found) {
throw IllegalArgumentException("Collection contains more than one matching element: $single, $element")
}
single = element
found = true
}
}
return single
}
val REMOTE_KIND_FQN = RPC_FQN.child("RemoteKind".name)
private val REMOTE_OBJECT_FQN = FqName.fromSegments(listOf("fleet", "rpc", "core", "RemoteObject"))
private val REMOTE_RESOURCE_FQN = FqName.fromSegments(listOf("fleet", "rpc", "core", "RemoteResource"))
@@ -0,0 +1,57 @@
package com.jetbrains.fleet.rpc.plugin.ir.remoteKind
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irCallConstructor
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import com.jetbrains.fleet.rpc.plugin.ir.CompilerPluginContext
import com.jetbrains.fleet.rpc.plugin.ir.util.RPC_FQN
import com.jetbrains.fleet.rpc.plugin.ir.util.name
import org.jetbrains.kotlin.ir.builders.irBoolean
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.isNullable
internal fun IrBuilderWithScope.handleSpecialTypes(irType: IrType, context: CompilerPluginContext, debugInfo: String): IrExpression? {
return context.fqnToRemoteKind(irType.classFqName)?.let { constructor ->
val elementKindType = (irType as IrSimpleType).arguments.first().typeOrFail
val remoteKind = toRemoteKind(elementKindType, context, debugInfo)
irCallConstructor(constructor, listOf(remoteKind.type)).apply {
arguments[0] = remoteKind
arguments[1] = irBoolean(irType.isNullable())
}
}
}
private fun CompilerPluginContext.fqnToRemoteKind(fqName: FqName?) = cache.remember {
val remoteKindFlowConstructor = pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "Flow")), false)
)!!.constructors.first()
val remoteKindReceiveChannelConstructor = pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "ReceiveChannel")), false)
)!!.constructors.first()
val remoteKindSendChannelConstructor = pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "SendChannel")), false)
)!!.constructors.first()
val remoteKindDeferredConstructor = pluginContext.referenceClass(
ClassId(RPC_FQN, FqName.fromSegments(listOf("RemoteKind", "Deferred")), false)
)!!.constructors.first()
mapOf(
FLOW_FQN to remoteKindFlowConstructor,
RECEIVE_CHANNEL_FQN to remoteKindReceiveChannelConstructor,
SEND_CHANNEL_FQN to remoteKindSendChannelConstructor,
DEFERRED_FQN to remoteKindDeferredConstructor,
)
}[fqName]
internal val FLOW_FQN = FqName.fromSegments(listOf("kotlinx", "coroutines", "flow", "Flow"))
internal val RESOURCE_FQN = FqName.fromSegments(listOf("fleet", "util", "async", "Resource"))
private val RECEIVE_CHANNEL_FQN = FqName.fromSegments(listOf("kotlinx", "coroutines", "channels", "ReceiveChannel"))
private val SEND_CHANNEL_FQN = FqName.fromSegments(listOf("kotlinx", "coroutines", "channels", "SendChannel"))
private val DEFERRED_FQN = FqName.fromSegments(listOf("kotlinx", "coroutines", "Deferred"))
@@ -0,0 +1,7 @@
package com.jetbrains.fleet.rpc.plugin.ir.util
class Cache {
private val cache = mutableMapOf<Any, Any?>()
fun <T> remember(block: () -> T): T = cache.getOrPut(block) { block() } as T
}
@@ -0,0 +1,48 @@
package com.jetbrains.fleet.rpc.plugin.ir.util
import com.jetbrains.fleet.rpc.plugin.ir.remoteKind.FLOW_FQN
import com.jetbrains.fleet.rpc.plugin.ir.remoteKind.RESOURCE_FQN
import org.jetbrains.kotlin.backend.wasm.ir2wasm.allSuperInterfaces
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.superTypes
fun collectRpcMethods(irClass: IrClass): List<IrSimpleFunction> {
return irClass.allSuperInterfaces().flatMap { superType ->
getRpcMethods(superType)
}
}
/**
* /not suspend/ fun(): Flow<*>
*/
fun IrSimpleFunction.isNonSuspendFlowFunction(): Boolean {
return !isSuspend && returnType.isClassType(FLOW_FQN.toUnsafe(), false)
}
/**
* /not suspend/ fun(): Resource<out RemoteResource>
*/
fun IrSimpleFunction.isNonSuspendResourceFunction(): Boolean {
return when {
isSuspend -> false
!returnType.isClassType(RESOURCE_FQN.toUnsafe(), false) -> false
else -> {
when (val arg = (returnType as IrSimpleType).arguments.first()) {
is IrStarProjection -> false
is IrTypeProjection -> {
arg.type.superTypes().any { it.classFqName == REMOTE_RESOURCE_FQN }
}
}
}
}
}
private val FUN_NAMES_TO_SKIP = listOf("equals", "hashCode", "toString", "dispatch", "signature")
private fun getRpcMethods(rpcClass: IrClass): List<IrSimpleFunction> {
return rpcClass.declarations.filterIsInstance<IrSimpleFunction>().filter { declaration ->
!declaration.isFakeOverride && declaration.overriddenSymbols.isEmpty() && declaration.name.identifier !in FUN_NAMES_TO_SKIP
}
}
@@ -0,0 +1,250 @@
package com.jetbrains.fleet.rpc.plugin.ir.util
import com.jetbrains.fleet.rpc.plugin.ir.CompilerPluginContext
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.ir.addDispatchReceiver
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.types.typeOrFail
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isAnnotationWithEqualFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
val RPC_FQN = FqName.fromSegments(listOf("fleet", "rpc"))
val RPC_ANNOTATION_FQN = RPC_FQN.child("Rpc".name)
val RPC_CORE_FQN: FqName = RPC_FQN.child("core".name)
val REMOTE_RESOURCE_FQN: FqName = RPC_CORE_FQN.child("RemoteResource".name)
val String.name: Name
get() = Name.identifier(this)
fun irGet(type: IrType, symbol: IrValueSymbol, origin: IrStatementOrigin?): IrExpression {
return IrGetValueImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
type,
symbol,
origin
)
}
fun irGet(variable: IrValueDeclaration, origin: IrStatementOrigin? = null): IrExpression {
return irGet(variable.type, variable.symbol, origin)
}
fun irCall(symbol: IrFunctionSymbol, origin: IrStatementOrigin? = null): IrCallImpl {
return IrCallImpl.fromSymbolOwner(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
type = symbol.owner.returnType,
symbol = symbol as IrSimpleFunctionSymbol,
origin = origin,
)
}
fun IrClass.hasRpcAnnotation() =
this.annotations.any { it.isAnnotationWithEqualFqName(RPC_ANNOTATION_FQN) }
fun IrPluginContext.buildClassBase(customizer: IrClassBuilder.() -> Unit) =
irFactory.buildClass {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
origin = IrDeclarationOrigin.DEFINED
kind = ClassKind.CLASS
modality = Modality.FINAL
customizer()
}.also {
thisReceiver(it)
constructor(it)
initializer(it)
}
private fun IrPluginContext.thisReceiver(irClass: IrClass): IrValueParameter =
irFactory.createValueParameter(
startOffset = SYNTHETIC_OFFSET,
endOffset = SYNTHETIC_OFFSET,
origin = IrDeclarationOrigin.INSTANCE_RECEIVER,
symbol = IrValueParameterSymbolImpl(),
name = SpecialNames.THIS,
type = IrSimpleTypeImpl(irClass.symbol, false, emptyList(), emptyList()),
varargElementType = null,
isCrossinline = false,
isNoinline = false,
isHidden = false,
isAssignable = false,
kind = IrParameterKind.DispatchReceiver
).also {
it.parent = irClass
irClass.thisReceiver = it
}
private fun IrPluginContext.constructor(irClass: IrClass): IrConstructor =
irClass.addConstructor {
isPrimary = true
returnType = irClass.defaultType
}.apply {
parent = irClass
body = irFactory.createBlockBody(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).apply {
statements += IrDelegatingConstructorCallImpl.fromSymbolOwner(
SYNTHETIC_OFFSET,
SYNTHETIC_OFFSET,
irBuiltIns.anyType,
irBuiltIns.anyClass.constructors.first(),
typeArgumentsCount = 0,
)
statements += IrInstanceInitializerCallImpl(
SYNTHETIC_OFFSET,
SYNTHETIC_OFFSET,
irClass.symbol,
irBuiltIns.unitType
)
}
}
private fun IrPluginContext.initializer(irClass: IrClass): IrAnonymousInitializer =
irFactory.createAnonymousInitializer(
SYNTHETIC_OFFSET, SYNTHETIC_OFFSET,
origin = IrDeclarationOrigin.DEFINED,
symbol = IrAnonymousInitializerSymbolImpl(),
isStatic = false
).apply {
parent = irClass
}
fun IrPluginContext.getProxyType(): IrSimpleType { // typealias Proxy = suspend (String, Array<Any?>) -> Any?
val stringType = irBuiltIns.stringType
val nullableAnyType = irBuiltIns.anyNType.makeNullable()
val anyArrayType = irBuiltIns.arrayClass.typeWith(nullableAnyType)
return irBuiltIns.suspendFunctionN(2)
.typeWith(/*thisType, */stringType, anyArrayType, nullableAnyType)
}
fun IrBuilderWithScope.createArrayOfExpression(
arrayElementType: IrType,
arrayElements: List<IrExpression>,
pluginContext: IrPluginContext
): IrExpression {
val arrayType = pluginContext.irBuiltIns.arrayClass.typeWith(arrayElementType)
val varargOfElements = IrVarargImpl(startOffset, endOffset, arrayType, arrayElementType, arrayElements)
val typeArguments = listOf(arrayElementType)
return irCall(pluginContext.irBuiltIns.arrayOf, arrayType, typeArguments = typeArguments).apply {
arguments[0] = varargOfElements
}
}
fun IrPluginContext.addFunctionOverride(originalFunction: IrSimpleFunction,
parentClass: IrClass,
block: (IrSimpleFunction) -> Unit) {
irFactory.buildFun {
name = originalFunction.name
returnType = originalFunction.returnType
modality = Modality.FINAL
}.also { function ->
function.isSuspend = originalFunction.isSuspend
function.overriddenSymbols = listOf(originalFunction.symbol)
function.parent = parentClass
for (typeParameter in originalFunction.typeParameters) {
function.addTypeParameter {
updateFrom(typeParameter)
superTypes += typeParameter.superTypes
}
}
// Prepend dispatch receiver (should be first in parameter list)
function.parameters += function.buildReceiverParameter {
type = parentClass.defaultType
}
for (parameter in originalFunction.parameters) {
// Dispatch receiver was specified right above
// TODO review: maybe this dispatch receiver is already present with proper type on original function? (then this can be simplified)
if (parameter.kind != IrParameterKind.DispatchReceiver) {
function.addValueParameter {
name = parameter.name
updateFrom(parameter)
}
}
}
block(function)
parentClass.declarations += function
}
}
fun IrFunction.generateBody(context: CompilerPluginContext, block: IrBlockBodyBuilder.() -> Unit) {
body = DeclarationIrBuilder(context.pluginContext, symbol).irBlockBody {
block()
}
}
/**
* Creates a lambda based on the provided function type.
*
* @param type type of the function
*/
internal fun IrPluginContext.irLambda(
symbol: IrSymbolOwner,
parent: IrDeclarationParent,
type: IrSimpleType,
// Default values assume the `type` parameter is `FunctionN` class type (arg types first, last one is return type)
parameterTypes: List<IrType> = type.arguments.dropLast(1).map { it.typeOrFail },
returnType: IrType = type.arguments.last().typeOrFail,
block: (IrFunction) -> Unit,
): IrFunctionExpressionImpl {
return IrFunctionExpressionImpl(
startOffset = symbol.startOffset,
endOffset = symbol.endOffset,
type = type,
function = irFactory.buildFun {
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
name = SpecialNames.NO_NAME_PROVIDED
visibility = DescriptorVisibilities.LOCAL
this.returnType = returnType
modality = Modality.FINAL
isSuspend = true
}.also {
it.parent = parent
parameterTypes.forEachIndexed { index, typeArgument ->
it.addValueParameter("p${index}", typeArgument.typeOrFail)
}
block(it)
},
origin = IrStatementOrigin.LAMBDA
)
}
@@ -0,0 +1 @@
com.jetbrains.fleet.rpc.plugin.RpcCommandLineProcessor
@@ -0,0 +1 @@
com.jetbrains.fleet.rpc.plugin.RpcComponentRegistrar