├── .github └── workflows │ ├── Build.yml │ └── Release.yml ├── .gitignore ├── .idea ├── icon.png └── markdown-navigator.xml ├── BridgeFeatures.md ├── LICENSE ├── README-chs.md ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── PublishingHelpers.kt │ ├── Versions.kt │ ├── embeddable.kt │ └── targets.kt ├── compiler-plugin-embeddable └── build.gradle.kts ├── compiler-plugin ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── him188 │ │ │ └── kotlin │ │ │ └── jvm │ │ │ └── blocking │ │ │ └── bridge │ │ │ └── compiler │ │ │ └── diagnostic │ │ │ └── BlockingBridgeErrors.java │ └── kotlin │ │ └── me │ │ └── him188 │ │ └── kotlin │ │ └── jvm │ │ └── blocking │ │ └── bridge │ │ └── compiler │ │ ├── BridgeCompilerConfigurationKeys.kt │ │ ├── backend │ │ ├── ir │ │ │ ├── analyzeCapabilityForGeneratingBridges.kt │ │ │ ├── generator.kt │ │ │ ├── loweringPasses.kt │ │ │ └── util.kt │ │ └── resolve │ │ │ ├── analyzeCapabilityForGeneratingBridges.kt │ │ │ ├── analyzing.kt │ │ │ ├── generateDefaultIfNeeded.kt │ │ │ └── util.kt │ │ ├── diagnostic │ │ ├── BlockingBridgeDeclarationChecker.kt │ │ └── BlockingBridgeErrorsRendering.kt │ │ └── extensions │ │ ├── BridgeCodegenCliExtension.kt │ │ ├── BridgeCommandLineProcessor.kt │ │ ├── BridgeComponentRegistrar.kt │ │ └── BridgeIrGenerationExtension.kt │ └── test │ └── kotlin │ ├── compiler │ ├── AbiAnnotationsTest.kt │ ├── AbstractCompilerTest.kt │ ├── BasicsTest.kt │ ├── BridgeAnnotationOnClassTest.kt │ ├── BridgeAnnotationOnFileTest.kt │ ├── BridgeForModule.kt │ ├── InheritanceTest.kt │ ├── StaticTest.kt │ ├── VisibilityAndModalityTest.kt │ ├── WithJavaTest.kt │ └── unit │ │ ├── AbstractUnitCoercionTest.kt │ │ └── UnitCoercionTest.kt │ └── util.kt ├── gradle-plugin ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── me │ │ └── him188 │ │ └── kotlin │ │ └── jvm │ │ └── blocking │ │ └── bridge │ │ ├── BlockingBridgePluginExtension.kt │ │ ├── Extensions.kt │ │ └── JvmBlockingBridgeGradlePlugin.kt │ └── resources │ └── META-INF │ └── services_ │ └── org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ide-plugin ├── build.gradle.kts ├── libs │ └── ide-common.jar ├── run │ ├── below8 │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── Below8.kt │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── mpp │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── commonMain │ │ │ └── kotlin │ │ │ └── common.kt │ ├── mppCommon │ │ ├── build.gradle.kts │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── src │ │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ ├── Test.kt │ │ │ │ └── Top.kt │ │ │ ├── jsMain │ │ │ └── kotlin │ │ │ │ └── Main.kt │ │ │ ├── jsTest │ │ │ └── kotlin │ │ │ │ └── GreetingTest.kt │ │ │ └── jvmMain │ │ │ ├── java │ │ │ └── Java.java │ │ │ └── kotlin │ │ │ └── Kt.kt │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── CallsFromJava.java │ │ └── kotlin │ │ ├── AnnotationOnClass.kt │ │ ├── AnnotationOnFile.kt │ │ ├── AnnotationOnInterface.kt │ │ ├── Overloads.kt │ │ ├── SyntheticStaticInCompanion.kt │ │ ├── TestEffectivePublic.kt │ │ ├── TestRet.kt │ │ ├── main.kt │ │ └── ret.kt └── src │ └── main │ ├── kotlin │ └── me │ │ └── him188 │ │ └── kotlin │ │ └── jvm │ │ └── blocking │ │ └── bridge │ │ └── ide │ │ ├── BridgeInlayHintsCollector.kt │ │ ├── BridgeModuleCacheService.kt │ │ ├── BridgeProjectImportListener.kt │ │ ├── BridgeStorageComponentContainerContributor.kt │ │ ├── Icons.kt │ │ ├── JvmBlockingBridgePsiAugmentProvider.kt │ │ ├── bridgeExt.kt │ │ ├── fix │ │ ├── BlockingBridgeBundle.kt │ │ ├── QuickFixRegistrar.kt │ │ ├── RemoveJvmBlockingBridgeFix.kt │ │ └── RemoveJvmSyntheticFix.kt │ │ └── line │ │ └── marker │ │ └── BlockingBridgeLineMarkerProvider.kt │ └── resources │ ├── META-INF │ └── plugin.xml │ ├── icons │ ├── bridgedSuspendCall.svg │ └── bridgedSuspendCall_dark.svg │ └── messages │ └── BlockingBridgeBundle.properties ├── runtime ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ ├── me │ │ └── him188 │ │ │ └── kotlin │ │ │ └── jvm │ │ │ └── blocking │ │ │ └── bridge │ │ │ └── JvmBlockingBridge.kt │ │ └── net │ │ └── mamoe │ │ └── kjbb │ │ └── JvmBlockingBridge.kt │ └── jvmMain │ └── kotlin │ ├── me │ └── him188 │ │ └── kotlin │ │ └── jvm │ │ └── blocking │ │ └── bridge │ │ ├── GeneratedBlockingBridge.kt │ │ ├── JvmBlockingBridge.kt │ │ └── internal │ │ └── RunSuspend.kt │ └── net │ └── mamoe │ └── kjbb │ └── JvmBlockingBridge.kt └── settings.gradle.kts /.github/workflows/Build.yml: -------------------------------------------------------------------------------- 1 | name: Gradle CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-on-windows: 7 | runs-on: windows-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Set up JDK 11 11 | uses: actions/setup-java@v1 12 | with: 13 | java-version: 11 14 | - name: Grant execute permission for gradlew 15 | run: chmod +x gradlew 16 | - name: Build with Gradle 17 | run: ./gradlew build --scan 18 | 19 | build-on-macos: 20 | runs-on: macos-11 21 | steps: 22 | - uses: maxim-lobanov/setup-xcode@v1 23 | with: 24 | xcode-version: '13.1' 25 | - uses: actions/checkout@v2 26 | - name: Set up JDK 11 27 | uses: actions/setup-java@v1 28 | with: 29 | java-version: 11 30 | - name: Grant execute permission for gradlew 31 | run: chmod +x gradlew 32 | - name: Build with Gradle 33 | run: ./gradlew build --scan 34 | -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Release Publihsing 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish-runtime-on-windows: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - run: chmod +x gradlew 17 | - run: ./gradlew clean --scan 18 | 19 | - name: previewPublication 20 | run: ./gradlew previewPublication --no-parallel --info --scan 21 | env: 22 | PUBLICATION_CREDENTIALS: ${{ secrets.PUBLICATION_CREDENTIALS }} 23 | 24 | - run: ./gradlew assemble --scan # do not run tests 25 | 26 | - name: publishMingwX64PublicationToMavenRepository 27 | run: ./gradlew publishMingwX64PublicationToMavenRepository --no-parallel --info --scan 28 | env: 29 | PUBLICATION_CREDENTIALS: ${{ secrets.PUBLICATION_CREDENTIALS }} 30 | 31 | publish-others-on-macos: 32 | runs-on: macos-11 33 | needs: 34 | - publish-runtime-on-windows 35 | steps: 36 | - uses: maxim-lobanov/setup-xcode@v1 37 | with: 38 | xcode-version: '13.1' 39 | - uses: actions/checkout@v2 40 | - uses: actions/setup-java@v1 41 | with: 42 | java-version: 11 43 | - run: chmod +x gradlew 44 | - run: ./gradlew clean --scan 45 | 46 | - name: previewPublication 47 | run: ./gradlew previewPublication --no-parallel --info --scan 48 | env: 49 | PUBLICATION_CREDENTIALS: ${{ secrets.PUBLICATION_CREDENTIALS }} 50 | 51 | - run: ./gradlew assemble --scan # do not run tests 52 | 53 | - name: publish 54 | run: ./gradlew publish 55 | -Dsigner.workdir=/tmp/gpg 56 | --no-parallel 57 | --info --scan 58 | env: 59 | PUBLICATION_CREDENTIALS: ${{ secrets.PUBLICATION_CREDENTIALS }} 60 | 61 | - name: Publish Gradle plugin 62 | run: > 63 | ./gradlew 64 | publishPlugins --scan 65 | -Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} 66 | -Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} 67 | 68 | - name: Gradle closeRepository 69 | run: > 70 | ./gradlew 71 | :closeRepository --info --scan 72 | -Dsonatype_key=${{ secrets.SONATYPE_USER }} 73 | -Dsonatype_password=${{ secrets.SONATYPE_KEY }} 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Kotlin template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | ### JetBrains template 28 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 29 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 30 | 31 | # User-specific stuff 32 | .idea/**/workspace.xml 33 | .idea/**/tasks.xml 34 | .idea/**/usage.statistics.xml 35 | .idea/**/dictionaries 36 | .idea/**/shelf 37 | 38 | # Generated files 39 | .idea/**/contentModel.xml 40 | 41 | # Sensitive or high-churn files 42 | .idea/**/dataSources/ 43 | .idea/**/dataSources.ids 44 | .idea/**/dataSources.local.xml 45 | .idea/**/sqlDataSources.xml 46 | .idea/**/dynamic.xml 47 | .idea/**/uiDesigner.xml 48 | .idea/**/dbnavigator.xml 49 | 50 | # Gradle 51 | .idea/**/gradle.xml 52 | .idea/**/libraries 53 | 54 | # Gradle and Maven with auto-import 55 | # When using Gradle or Maven with auto-import, you should exclude module files, 56 | # since they will be recreated, and may cause churn. Uncomment if using 57 | # auto-import. 58 | # .idea/artifacts 59 | # .idea/compiler.xml 60 | # .idea/jarRepositories.xml 61 | # .idea/modules.xml 62 | # .idea/*.iml 63 | # .idea/modules 64 | # *.iml 65 | # *.ipr 66 | 67 | # CMake 68 | cmake-build-*/ 69 | 70 | # Mongo Explorer plugin 71 | .idea/**/mongoSettings.xml 72 | 73 | # File-based project format 74 | *.iws 75 | 76 | # IntelliJ 77 | out/ 78 | 79 | # mpeltonen/sbt-idea plugin 80 | .idea_modules/ 81 | 82 | # JIRA plugin 83 | atlassian-ide-plugin.xml 84 | 85 | # Cursive Clojure plugin 86 | .idea/replstate.xml 87 | 88 | # Crashlytics plugin (for Android Studio and IntelliJ) 89 | com_crashlytics_export_strings.xml 90 | crashlytics.properties 91 | crashlytics-build.properties 92 | fabric.properties 93 | 94 | # Editor-based Rest Client 95 | .idea/httpRequests 96 | 97 | # Android studio 3.1+ serialized cache file 98 | .idea/caches/build_file_checksums.ser 99 | 100 | ### Gradle template 101 | .gradle 102 | /build/ 103 | 104 | # Ignore Gradle GUI config 105 | gradle-app.setting 106 | 107 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 108 | !gradle-wrapper.jar 109 | 110 | # Cache of project 111 | .gradletasknamecache 112 | 113 | # GPG private key 114 | /keys 115 | 116 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 117 | # gradle/wrapper/gradle-wrapper.properties -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Him188/kotlin-jvm-blocking-bridge/1d30ac7d8420783e6275f116cbb264df4932aa5e/.idea/icon.png -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /README-chs.md: -------------------------------------------------------------------------------- 1 | # kotlin-jvm-blocking-bridge 2 | 3 | **[ENGLISH 英文](./README.md)** 4 | 5 | 为 Kotlin `suspend` 函数快速生成‘阻塞式方法桥’的 Kotlin 编译器插件。 6 | 7 | ## 截图 8 | 9 |
10 | 11 | 点击左侧箭头查看 12 | 13 | Kotlin 挂起函数: 14 | ![image_2.png](https://i.loli.net/2020/08/08/d5cYwhQqeuj8Nvf.png) 15 | 16 | 阻塞式方法桥调用: 17 | ![image.png](https://i.loli.net/2020/08/08/tJyGeOcB8E4muQ5.png) 18 | 19 | 文档和跳转支持: 20 | ![image_1.png](https://i.loli.net/2020/08/08/koCl6zj4OAJ5aUN.png) 21 | 22 |
23 | 24 | ## 动机 25 | 26 | Kotlin `suspend` 函数编译后会被加上一个额外参数 `$completion: Continuation`。在 Java 调用这样的方法很困难,我们可以做一些兼容: 27 | 28 | ```kotlin 29 | suspend fun downloadImage(): Image 30 | ``` 31 | 32 | 我们可以添加一个‘阻塞式方法桥’: 33 | 34 | ```kotlin 35 | @JvmName("downloadImage") // 避免引用歧义 36 | fun downloadImageBlocking(): Image = runBlocking { downloadImage() } 37 | ``` 38 | 39 | 这样,Java 使用者可以直接通过 `downloadImage()` 调用,就像在 Kotlin 调用 `suspend` 函数。即使这损失了一些性能,但通常我们不在乎它。 40 | 41 | 然而,这也带来了一些问题: 42 | 43 | - KDoc 需要从原函数复制到额外添加的函数,并且修改时要同时修改两者 44 | - 修改函数签名(参数,修饰符,返回值,注解)变得十分不便 45 | - 我们不希望将 `downloadImageBlocking` 暴露给 Kotlin 调用方,但没有方法隐藏它们 46 | 一个不太好的解决方法是添加 `RequiresOptIn`: 47 | ```kotlin 48 | @RequiresOptIn(level = ERROR) 49 | annotation class JavaFriendlyApi 50 | 51 | @JavaFriendlyApi // 于是当 Kotlin 调用者调用这个函数时,IDE 会报告 ERROR 级别的错误。尽管 Kotlin 仍然能看到这些方法。 52 | @JvmName("downloadImage") // 避免引用歧义 53 | fun downloadImageBlocking(): Image = runBlocking { downloadImage() } 54 | ``` 55 | 但这似乎不是最好的----我们要重复 `JavaFriendlyApi` 这样的注解很多次,还需要到处使用它。 56 | 57 | **本编译器插件设计为最小化这样兼容 Java 时所作的额外工作----仅需添加一个注解**: 58 | 59 | ```kotlin 60 | @JvmBlockingBridge 61 | suspend fun downloadImage(): Image 62 | ``` 63 | 64 | 编译器会帮助生成上述的‘阻塞式方法桥’ `fun downloadImage()`,使用‘相同’的方法签名(编译级的生成不会引起调用歧义),且更高效。 65 | 66 | ### Examples Of Usages 67 | 68 | 1. 提供在 Java 调用 `suspend` 函数的最简单方式: 69 | ```kotlin 70 | interface Image 71 | 72 | object ImageManager { 73 | @JvmStatic 74 | @JvmBlockingBridge 75 | suspend fun getImage(): Image 76 | } 77 | ``` 78 | ```java 79 | class Test { 80 | public static void main(String[] args){ 81 | Image image = ImageManager.getImage(); // just like in Kotlin, no need to implement Continuation. 82 | } 83 | } 84 | ``` 85 | 86 | 2. 在测试中,使用 `@JvmBlockingBridge` 来运行 `suspend` 的测试函数而不需要 `runBlocking`: 87 | 88 | ```kotlin 89 | @file:JvmBlockingBridge 90 | 91 | class SomeTests { 92 | @Test 93 | suspend fun test() { /* ... */ } 94 | } 95 | ``` 96 | 97 | ## 稳定性 98 | 99 | 编译器插件有超过 150 个单元测试来确保每一项功能的正常运行。 100 | 101 | 拥有 10 万行 Kotlin 代码的库 [mirai](https://github.com/mamoe/mirai) 大量地在各种情况下使用了这个编译器插件。mirai 拥有严格二进制兼容测试,正在被成千上万的用户使用。 102 | 这意味着 Kotlin Jvm Blocking Bridge 提供稳定的编译结果,而且适用于生产环境。 103 | 104 | ## 使用要求 105 | 106 | - Gradle(仅在 6.0+ 环境通过测试) 107 | - **Kotlin `1.4.20` 或更高** 108 | - IntelliJ IDEA 或 Android Studio(推荐保持新稳定版本) 109 | 110 | ## 现在体验 111 | 112 | ### 使用者 113 | 114 | **如果一个库使用了 Kotlin JVM Blocking Bridge,依赖方无需特别在意,可以就像普通库一样添加编译依赖使用。** 即依赖使用或不使用 KJBB 编译的库的流程都是一样的。 115 | 116 | ### 库和应用程序作者 117 | 118 | 如果你正开发一个库或应用程序,你需要安装 Gradle 插件来获取编译支持,和安装 IntelliJ IDEA 插件来获取编辑支持。 119 | 120 | #### **安装 IntelliJ IDEA (或 Android Studio) 插件** 121 | 122 | 本插件仅支持 IntelliJ IDEA 和 Android Studio。 123 | Eclipse 和 Visual Studio 或其他 IDE 均不受支持。 124 | 125 | 一键安装:,或者也可以手动安装: 126 | 127 | 1. 打开 `File->Settings->Plugins->Marketplace` 128 | 2. 搜索 `Kotlin Jvm Blocking Bridge`,下载并安装 129 | 3. 重启 IDE 130 | 131 | #### **安装 Gradle 插件.** 132 | 133 | 在 `build.gradle` 或 `build.gradle.kts` 中添加: 134 | 135 | ```kotlin 136 | plugins { 137 | id("me.him188.kotlin-jvm-blocking-bridge") version "VERSION" 138 | } 139 | ``` 140 | 141 | 可在 [releases](https://github.com/Him188/kotlin-jvm-blocking-bridge/releases) 获取 `VERSION`, 如 `3.1.0-180.1`. 142 | 143 | You also need to add runtime dependency as follows. 144 | Please make sure you have it in runtime (use `implementation` or `api`, don't use `compileOnly`), because the compiled 145 | bridges needs the runtime library. 146 | 147 | 也许添加如下的依赖库。 148 | 请保证运行环境包含该库 (使用 `implementation` 或 `api`, 不要使用 `compileOnly`),因为编译的方法桥需要此依赖库。 149 | 150 | ```kotlin 151 | implementation("me.him188:kotlin-jvm-blocking-bridge-runtime:VERSION") 152 | ``` 153 | 154 | 对于 JVM 项目: 155 | ```kotlin 156 | dependencies { 157 | implementation("me.him188:kotlin-jvm-blocking-bridge-runtime:VERSION") 158 | } 159 | ``` 160 | 161 | 对于多平台项目, 为 `commonMain` 添加即可将依赖添加到所有编译目标: 162 | 163 | ```kotlin 164 | kotlin.sourceSets { 165 | commonMain { 166 | dependencies { 167 | implementation("me.him188:kotlin-jvm-blocking-bridge-runtime:VERSION") 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | > 如果 Gradle 无法下载这个插件,请在 `settings.gradle` 或 `settings.gradle.kts` 中添加 `gradlePluginPortal()`: 174 | > ```kotlin 175 | > pluginManagement { 176 | > repositories { 177 | > gradlePluginPortal() 178 | > } 179 | > } 180 | > ``` 181 | 182 | ## 支持的编译器后端 183 | 184 | Kotlin 拥有两个编译器后端,旧 `JVM` 和新 `IR`(Internal Representation)。 185 | Kotlin 在 1.5 及以前使用 `JVM` 后端,在 1.6 及以后使用 `IR` 后端。 186 | 187 | 本插件同时支持这两个后端。在两个后端产生的编译结果都是相同的。 188 | 189 | K2 暂未支持。 190 | 191 | ## 模块 192 | 193 | 如果你感兴趣于这个项目的原理,本章节可能会有帮助。当然你也可以直接使用插件了。 194 | 195 | - **运行时库** *提供 @JvmBlockingBridge 注解* 196 | - **编译器插件** *提供编译代码生成,编译目标为 JVM 字节码或 Kotlin IR* 197 | - **ide-plugin** 198 | 199 | **在 [BridgeFeatures.md](BridgeFeatures.md) 阅读规范** 200 | 201 | ### 运行时库 202 | 203 | - 提供 `public annotation class me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge` 204 | - 提供 `internal annotation class me.him188.kotlin.jvm.blocking.bridge.GeneratedBlockingBridge`,由编译器插件自动添加到生成的方法桥上. 205 | - 提供编译后的阻塞式方法桥需要调用的一些库函数. 206 | 207 | ### 编译器插件 208 | 209 | 对于 Kotlin `suspend` 函数: 210 | 211 | ```kotlin 212 | @JvmBlockingBridge 213 | suspend fun test(a1: Int, a2: Any): String 214 | ``` 215 | 216 | 本编译器插件生成与原函数具有‘相同’签名的‘阻塞式方法桥’ (仅 Java 可见) 217 | 218 | ```kotlin 219 | @GeneratedBlockingBridge 220 | fun test(a1: Int, a2: Any): String = `$runSuspend$` { 221 | test(a1, a2) // 调用原 suspend `test` 222 | } // `$runSuspend$` 是一个运行时库中的函数, 因此不依赖 kotlinx-coroutines-core. 223 | ``` 224 | 225 | ### IDE (IntelliJ) 插件 226 | 227 | - 让 Java 用户能引用阻塞式方法桥 (即使它们还没有生成) 228 | - 为 Kotlin 用户隐藏生成的阻塞式方法桥 (即使它们已经生成) 229 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage", "LocalVariableName") 2 | 3 | plugins { 4 | kotlin("multiplatform") apply false 5 | id("me.him188.maven-central-publish") version "1.0.0" apply false 6 | kotlin("kapt") apply false 7 | kotlin("plugin.serialization") version Versions.kotlin apply false 8 | id("com.gradle.plugin-publish") version "0.18.0" apply false 9 | id("io.codearte.nexus-staging") version "0.22.0" 10 | id("java") 11 | id("com.github.johnrengelman.shadow") version "7.1.0" apply false 12 | } 13 | 14 | allprojects { 15 | group = Versions.publicationGroup 16 | description = 17 | "Kotlin compiler plugin for generating blocking bridges for calling suspend functions from Java with minimal effort" 18 | version = Versions.project 19 | 20 | repositories { 21 | mavenCentral() 22 | gradlePluginPortal() 23 | } 24 | } 25 | 26 | nexusStaging { 27 | packageGroup = rootProject.group.toString() 28 | username = System.getProperty("sonatype_key") ?: project.findProperty("sonatype.key")?.toString() 29 | password = System.getProperty("sonatype_password") ?: project.findProperty("sonatype.password")?.toString() 30 | } 31 | 32 | subprojects { 33 | afterEvaluate { 34 | setupKotlinSourceSetsSettings() 35 | } 36 | } 37 | 38 | extensions.findByName("buildScan")?.withGroovyBuilder { 39 | setProperty("termsOfServiceUrl", "https://gradle.com/terms-of-service") 40 | setProperty("termsOfServiceAgree", "yes") 41 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | gradlePluginPortal() 8 | } 9 | 10 | kotlin { 11 | sourceSets.all { 12 | languageSettings.optIn("kotlin.Experimental") 13 | languageSettings.optIn("kotlin.RequiresOptIn") 14 | } 15 | } 16 | 17 | dependencies { 18 | fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" 19 | fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" 20 | 21 | compileOnly(gradleApi()) 22 | api("org.jetbrains.kotlin:kotlin-gradle-plugin:${version("kotlin")}") 23 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0") 24 | 25 | compileOnly("com.github.jengelman.gradle.plugins:shadow:6.0.0") 26 | } 27 | 28 | fun version(name: String): String = project.rootDir.resolve("src/main/kotlin/Versions.kt").readText() 29 | .substringAfter("$name = \"", "") 30 | .substringBefore("\"", "") 31 | .also { 32 | check(it.isNotBlank()) { "Cannot find version $name" } 33 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/PublishingHelpers.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RemoveRedundantBackticks") 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.tasks.testing.Test 5 | import org.gradle.kotlin.dsl.getByName 6 | import org.gradle.kotlin.dsl.withType 7 | import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension 8 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 9 | 10 | @Suppress("NOTHING_TO_INLINE") 11 | inline fun Project.setupKotlinSourceSetsSettings() { 12 | kotlin.runCatching { 13 | extensions.getByType(KotlinProjectExtension::class.java) 14 | }.getOrNull()?.run { 15 | sourceSets.all { 16 | 17 | languageSettings.apply { 18 | progressiveMode = true 19 | 20 | optIn("kotlin.Experimental") 21 | optIn("kotlin.RequiresOptIn") 22 | 23 | optIn("kotlin.ExperimentalUnsignedTypes") 24 | optIn("kotlin.experimental.ExperimentalTypeInference") 25 | optIn("kotlin.contracts.ExperimentalContracts") 26 | } 27 | } 28 | } 29 | 30 | kotlin.runCatching { tasks.getByName("test", Test::class) }.getOrNull()?.apply { 31 | useJUnitPlatform() 32 | } 33 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Versions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate") 2 | 3 | object Versions { 4 | const val intellij = "2023.2" 5 | 6 | const val project = "3.1.0-191.1" 7 | const val idePlugin = "232-$project" 8 | 9 | const val kotlin = "1.9.10" 10 | val kotlinIdea: String? = null 11 | const val coroutines = "1.7.0" 12 | 13 | const val publicationGroup = "me.him188" 14 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/embeddable.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Some of the code is copied from JetBrains/Kotlin. Copyright JetBrains s.r.o. 3 | */ 4 | 5 | 6 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 7 | import org.gradle.api.Project 8 | import org.gradle.api.artifacts.Configuration 9 | import org.gradle.api.artifacts.ConfigurationContainer 10 | import org.gradle.api.file.DuplicatesStrategy 11 | import org.gradle.api.tasks.TaskProvider 12 | import org.gradle.kotlin.dsl.register 13 | import java.io.File 14 | 15 | const val kotlinEmbeddableRootPackage = "org.jetbrains.kotlin" 16 | 17 | val packagesToRelocate = 18 | listOf( 19 | "com.intellij", 20 | "com.google", 21 | "com.sampullara", 22 | "org.apache", 23 | "org.jdom", 24 | "org.picocontainer", 25 | "org.jline", 26 | "org.fusesource", 27 | "net.jpountz", 28 | "one.util.streamex", 29 | "it.unimi.dsi.fastutil", 30 | "kotlinx.collections.immutable" 31 | ) 32 | 33 | // The shaded compiler "dummy" is used to rewrite dependencies in projects that are used with the embeddable compiler 34 | // on the runtime and use some shaded dependencies from the compiler 35 | // To speed-up rewriting process we want to have this dummy as small as possible. 36 | // But due to the shadow plugin bug (https://github.com/johnrengelman/shadow/issues/262) it is not possible to use 37 | // packagesToRelocate list to for the include list. Therefore the exclude list has to be created. 38 | val packagesToExcludeFromDummy = 39 | listOf( 40 | "org/jetbrains/kotlin/**", 41 | "org/intellij/lang/annotations/**", 42 | "org/jetbrains/jps/**", 43 | "META-INF/**", 44 | "com/sun/jna/**", 45 | "com/thoughtworks/xstream/**", 46 | "javaslang/**", 47 | "*.proto", 48 | "messages/**", 49 | "net/sf/cglib/**", 50 | "one/util/streamex/**", 51 | "org/iq80/snappy/**", 52 | "org/jline/**", 53 | "org/xmlpull/**", 54 | "*.txt" 55 | ) 56 | 57 | @Suppress("NOTHING_TO_INLINE") // shadow version differs 58 | @PublishedApi 59 | internal inline fun ShadowJar.configureEmbeddableCompilerRelocation(withJavaxInject: Boolean = true) { 60 | relocate("com.google.protobuf", "org.jetbrains.kotlin.protobuf") 61 | packagesToRelocate.forEach { 62 | relocate(it, "$kotlinEmbeddableRootPackage.$it") 63 | } 64 | if (withJavaxInject) { 65 | relocate("javax.inject", "$kotlinEmbeddableRootPackage.javax.inject") 66 | } 67 | relocate("org.fusesource", "$kotlinEmbeddableRootPackage.org.fusesource") { 68 | // TODO: remove "it." after #KT-12848 get addressed 69 | exclude("org.fusesource.jansi.internal.CLibrary") 70 | } 71 | } 72 | 73 | @PublishedApi 74 | internal inline fun Project.compilerShadowJar( 75 | taskName: String, 76 | crossinline body: ShadowJar.() -> Unit, 77 | ): TaskProvider { 78 | 79 | // val compilerJar = configurations.getOrCreate("compilerJar") 80 | //dependencies.add(compilerJar.name, dependencies.project(":kotlin-compiler", configuration = "runtimeJar")) 81 | 82 | return tasks.register(taskName) { 83 | dependsOn("jar") 84 | destinationDirectory.set(File(buildDir, "libs")) 85 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 86 | from((tasks.getByName("shadowJar") as ShadowJar).configurations) 87 | from((tasks.getByName("shadowJar") as ShadowJar).source) 88 | 89 | // from(project.tasks.getByName("compileKotlin").outputs) 90 | // from(project.tasks.getByName("compileJava").outputs) 91 | body() 92 | } 93 | } 94 | 95 | fun ConfigurationContainer.getOrCreate(name: String): Configuration = findByName(name) ?: create(name) 96 | 97 | inline fun Project.embeddableCompiler( 98 | taskName: String = "embeddable", 99 | crossinline body: ShadowJar.() -> Unit = {}, 100 | ): TaskProvider { 101 | return compilerShadowJar(taskName) { 102 | group = "shadow" 103 | configureEmbeddableCompilerRelocation() 104 | body() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/targets.kt: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 2 | import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension 3 | import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension 4 | import org.jetbrains.kotlin.gradle.plugin.KotlinTarget 5 | 6 | val KotlinProjectExtension.targets: Iterable 7 | get() = when (this) { 8 | is KotlinSingleTargetExtension<*> -> listOf(this.target) 9 | is KotlinMultiplatformExtension -> targets 10 | else -> error("Unexpected 'kotlin' extension $this") 11 | } 12 | -------------------------------------------------------------------------------- /compiler-plugin-embeddable/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | 3 | plugins { 4 | id("me.him188.maven-central-publish") 5 | kotlin("jvm") 6 | kotlin("plugin.serialization") 7 | signing 8 | id("com.github.johnrengelman.shadow") 9 | } 10 | 11 | kotlin.targets.asSequence() 12 | .flatMap { it.compilations } 13 | .filter { it.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm } 14 | .map { it.kotlinOptions } 15 | .filterIsInstance() 16 | .forEach { it.jvmTarget = "1.8" } 17 | 18 | java { 19 | sourceCompatibility = JavaVersion.VERSION_1_8 20 | targetCompatibility = JavaVersion.VERSION_1_8 21 | } 22 | 23 | 24 | dependencies { 25 | api(project(":kotlin-jvm-blocking-bridge-compiler")) 26 | } 27 | 28 | embeddableCompiler() 29 | 30 | mavenCentralPublish { 31 | this.workingDir = rootProject.buildDir.resolve("temp/pub/").apply { mkdirs() } 32 | useCentralS01() 33 | singleDevGithubProject("Him188", "kotlin-jvm-blocking-bridge") 34 | licenseApacheV2() 35 | 36 | addProjectComponents = false 37 | publication { 38 | artifact(tasks.getByName("embeddable") as ShadowJar) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /compiler-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions 2 | import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType 3 | 4 | plugins { 5 | id("me.him188.maven-central-publish") 6 | kotlin("jvm") 7 | kotlin("kapt") 8 | kotlin("plugin.serialization") 9 | id("com.github.johnrengelman.shadow") 10 | } 11 | 12 | kotlin.targets.asSequence() 13 | .flatMap { it.compilations } 14 | .filter { it.platformType == KotlinPlatformType.jvm } 15 | .map { it.kotlinOptions } 16 | .filterIsInstance() 17 | .forEach { it.jvmTarget = "1.8" } 18 | 19 | java { 20 | sourceCompatibility = JavaVersion.VERSION_1_8 21 | targetCompatibility = JavaVersion.VERSION_1_8 22 | } 23 | 24 | dependencies includeInShadow@{ 25 | implementation(project(":kotlin-jvm-blocking-bridge-runtime")) 26 | } 27 | 28 | dependencies compileOnly@{ 29 | compileOnly(kotlin("stdlib")) // don't include stdlib in shadow 30 | compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlin}") 31 | kapt("com.google.auto.service:auto-service:1.0.1") 32 | compileOnly("com.google.auto.service:auto-service-annotations:1.0.1") 33 | } 34 | 35 | dependencies tests@{ 36 | testImplementation(project(":kotlin-jvm-blocking-bridge-runtime")) 37 | 38 | testImplementation(kotlin("reflect")) 39 | 40 | testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${Versions.kotlin}") 41 | testRuntimeOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlin}") // for debugger 42 | //testImplementation("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlin}") 43 | 44 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") 45 | 46 | testImplementation(kotlin("test-junit5")) 47 | 48 | testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.5.0") { 49 | exclude("org.jetbrains.kotlin", "kotlin-annotation-processing-embeddable") 50 | } 51 | testRuntimeOnly("org.jetbrains.kotlin:kotlin-annotation-processing-embeddable:${Versions.kotlin}") 52 | 53 | 54 | testImplementation("org.assertj:assertj-core:3.22.0") 55 | 56 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") 57 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") 58 | } 59 | 60 | embeddableCompiler() 61 | 62 | val test by tasks.getting(Test::class) { 63 | dependsOn(tasks.getByName("embeddable")) 64 | afterEvaluate { 65 | classpath = tasks.getByName("embeddable").outputs.files + classpath 66 | classpath = 67 | files(*classpath.filterNot { 68 | it.absolutePath.replace("\\", "/").removeSuffix("/").endsWith("build/classes/kotlin/main") 69 | || it.absolutePath.replace("\\", "/").removeSuffix("/").endsWith("build/classes/java/main") 70 | }.toTypedArray()) 71 | } 72 | } 73 | 74 | mavenCentralPublish { 75 | this.workingDir = rootProject.buildDir.resolve("temp/pub/").apply { mkdirs() } 76 | useCentralS01() 77 | singleDevGithubProject("Him188", "kotlin-jvm-blocking-bridge") 78 | licenseApacheV2() 79 | } -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/me/him188/kotlin/jvm/blocking/bridge/compiler/diagnostic/BlockingBridgeErrors.java: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; 5 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; 6 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; 7 | import org.jetbrains.kotlin.diagnostics.Errors; 8 | 9 | import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; 10 | import static org.jetbrains.kotlin.diagnostics.Severity.WARNING; 11 | 12 | 13 | public interface BlockingBridgeErrors { 14 | DiagnosticFactory0 BLOCKING_BRIDGE_PLUGIN_NOT_ENABLED = DiagnosticFactory0.create(WARNING); 15 | DiagnosticFactory0 INAPPLICABLE_JVM_BLOCKING_BRIDGE = DiagnosticFactory0.create(ERROR); 16 | DiagnosticFactory1 INLINE_CLASSES_NOT_SUPPORTED = DiagnosticFactory1.create(ERROR); 17 | DiagnosticFactory0 INTERFACE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); 18 | DiagnosticFactory0 REDUNDANT_JVM_BLOCKING_BRIDGE_ON_NON_PUBLIC_DECLARATIONS = DiagnosticFactory0.create(WARNING); 19 | DiagnosticFactory0 REDUNDANT_JVM_BLOCKING_BRIDGE_WITH_JVM_SYNTHETIC = DiagnosticFactory0.create(WARNING); 20 | DiagnosticFactory0 TOP_LEVEL_FUNCTIONS_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); 21 | 22 | DiagnosticFactory1 OVERRIDING_GENERATED_BLOCKING_BRIDGE = DiagnosticFactory1.create(WARNING); 23 | 24 | @Deprecated 25 | Object _init = new Object() { 26 | { 27 | Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages( 28 | BlockingBridgeErrors.class, 29 | BlockingBridgeErrorsRendering.INSTANCE 30 | ); 31 | } 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/BridgeCompilerConfigurationKeys.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler 2 | 3 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 4 | 5 | object JvmBlockingBridgeCompilerConfigurationKeys { 6 | @JvmStatic 7 | val ENABLE_FOR_MODULE = 8 | CompilerConfigurationKeyWithName("enableForModule") 9 | } 10 | 11 | // don't data 12 | class CompilerConfigurationKeyWithName(val name: String) : CompilerConfigurationKey(name) -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/ir/analyzeCapabilityForGeneratingBridges.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.BlockingBridgeAnalyzeResult 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.BlockingBridgeAnalyzeResult.* 5 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.isJvm8OrHigher 6 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 7 | import org.jetbrains.kotlin.backend.jvm.ir.isInlineClassType 8 | import org.jetbrains.kotlin.backend.jvm.ir.psiElement 9 | import org.jetbrains.kotlin.descriptors.effectiveVisibility 10 | import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI 11 | import org.jetbrains.kotlin.ir.declarations.IrClass 12 | import org.jetbrains.kotlin.ir.declarations.IrFunction 13 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 14 | import org.jetbrains.kotlin.ir.util.allParameters 15 | import org.jetbrains.kotlin.ir.util.hasAnnotation 16 | import org.jetbrains.kotlin.ir.util.module 17 | import org.jetbrains.kotlin.ir.util.parentClassOrNull 18 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 19 | import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME 20 | 21 | 22 | /** 23 | * Check whether a function is allowed to generate bridges with. 24 | * 25 | * The functions must 26 | * - be `final` or `open` 27 | * - have parent [IrClass] 28 | */ 29 | @OptIn(ObsoleteDescriptorBasedAPI::class) 30 | fun IrFunction.analyzeCapabilityForGeneratingBridges(ext: IBridgeConfiguration): BlockingBridgeAnalyzeResult { 31 | var annotationFromContainingClass = false 32 | 33 | val jvmBlockingBridgeAnnotationIr = 34 | jvmBlockingBridgeAnnotation() 35 | ?: jvmBlockingBridgeAnnotationOnContainingClass().also { annotationFromContainingClass = true } 36 | ?: kotlin.run { 37 | if (ext.enableForModule) null 38 | else return MissingAnnotationPsi 39 | } 40 | 41 | 42 | val jvmBlockingBridgeAnnotation = 43 | if (jvmBlockingBridgeAnnotationIr == null) null else 44 | jvmBlockingBridgeAnnotationIr.psiElement 45 | ?: psiElement 46 | ?: descriptor.findPsi() 47 | ?: return MissingAnnotationPsi 48 | 49 | if (this !is IrSimpleFunction) return Inapplicable(jvmBlockingBridgeAnnotation ?: return EnableForModule) 50 | 51 | val enableForModule = jvmBlockingBridgeAnnotation == null 52 | 53 | if (enableForModule || annotationFromContainingClass) { 54 | if (this.hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME)) return EnableForModule 55 | } 56 | 57 | fun impl(): BlockingBridgeAnalyzeResult { 58 | // fun must be suspend and applied to member function 59 | if (!isSuspend || name.isSpecial) { 60 | return Inapplicable(jvmBlockingBridgeAnnotation ?: return EnableForModule) 61 | } 62 | 63 | if (isGeneratedBlockingBridgeStub()) { 64 | // @JvmBlockingBridge and @GeneratedBlockingBridge both present 65 | return FromStub 66 | } 67 | 68 | if (!visibility.normalize().effectiveVisibility(descriptor, true).publicApi) { 69 | // effectively internal api 70 | return RedundantForNonPublicDeclarations(jvmBlockingBridgeAnnotation ?: return EnableForModule) 71 | } 72 | 73 | val containingClass = parentClassOrNull 74 | if (containingClass?.isValue == true) { 75 | // inside inline class not supported 76 | return InlineClassesNotSupported(jvmBlockingBridgeAnnotation ?: return EnableForModule, 77 | containingClass.descriptor) 78 | } 79 | 80 | allParameters.firstOrNull { it.type.isInlineClassType() }?.let { param -> 81 | // inline class param not yet supported 82 | return InlineClassesNotSupported( 83 | param.psiElement ?: jvmBlockingBridgeAnnotation ?: return EnableForModule, param.descriptor 84 | ) 85 | } 86 | 87 | if (containingClass?.isInterface == true) { // null means top-level, which is also accepted 88 | if (module.platform?.isJvm8OrHigher() != true) { 89 | // inside interface and JVM under 8 90 | return InterfaceNotSupported(jvmBlockingBridgeAnnotation ?: return EnableForModule) 91 | } 92 | } 93 | 94 | val overridden = this.findOverriddenDescriptorsHierarchically { 95 | it.analyzeCapabilityForGeneratingBridges(ext).shouldGenerate 96 | } 97 | 98 | if (overridden != null) { 99 | // super function has @ 100 | // generate only if this function has @, or implied from @ on class, which concluded as 'isReal' 101 | return OverridesSuper(isUserDeclaredFunction()) 102 | } 103 | 104 | // super function no @ 105 | // this function may has @ or implied from 106 | return if (isUserDeclaredFunction()) { 107 | // explicit 'override' then generate for it. 108 | Allowed 109 | } else { 110 | // implicit override by compiler, don't generate. 111 | BridgeAnnotationFromContainingDeclaration(null) 112 | } 113 | } 114 | 115 | val result = impl() 116 | if (annotationFromContainingClass) { 117 | if (!result.diagnosticPassed) { 118 | return BridgeAnnotationFromContainingDeclaration(result) 119 | } 120 | } 121 | return result 122 | } 123 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/ir/loweringPasses.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.BlockingBridgeAnalyzeResult 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 5 | import org.jetbrains.kotlin.backend.common.ClassLoweringPass 6 | import org.jetbrains.kotlin.backend.common.FileLoweringPass 7 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 8 | import org.jetbrains.kotlin.diagnostics.DiagnosticSink 9 | import org.jetbrains.kotlin.ir.declarations.IrClass 10 | import org.jetbrains.kotlin.ir.declarations.IrDeclaration 11 | import org.jetbrains.kotlin.ir.declarations.IrFile 12 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 13 | import org.jetbrains.kotlin.ir.util.companionObject 14 | import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat 15 | 16 | /** 17 | * For top-level functions 18 | */ 19 | class JvmBlockingBridgeFileLoweringPass( 20 | private val context: IrPluginContext, 21 | private val ext: IBridgeConfiguration, 22 | ) : FileLoweringPass { 23 | override fun lower(irFile: IrFile) { 24 | irFile.transformDeclarationsFlat { declaration -> 25 | declaration.transformFlat(context, ext) 26 | } 27 | } 28 | } 29 | 30 | internal fun IrDeclaration.transformFlat( 31 | context: IrPluginContext, 32 | ext: IBridgeConfiguration, 33 | ): List { 34 | val declaration = this 35 | if (declaration is IrSimpleFunction) { 36 | if (declaration.isGeneratedBlockingBridgeStub()) 37 | return listOf() 38 | 39 | val capability: BlockingBridgeAnalyzeResult = 40 | declaration.analyzeCapabilityForGeneratingBridges(ext) 41 | capability.createDiagnostic()?.let { diagnostic -> 42 | DiagnosticSink.THROW_EXCEPTION.report(diagnostic) 43 | } 44 | 45 | if (capability.shouldGenerate) { 46 | return declaration.followedBy(context.generateJvmBlockingBridges(declaration)) 47 | } 48 | } 49 | 50 | return listOf(declaration) 51 | } 52 | 53 | /** 54 | * For in-class functions 55 | */ 56 | class JvmBlockingBridgeClassLoweringPass( 57 | private val context: IrPluginContext, 58 | private val ext: IBridgeConfiguration, 59 | ) : ClassLoweringPass { 60 | override fun lower(irClass: IrClass) { 61 | irClass.transformDeclarationsFlat { declaration -> 62 | declaration.transformFlat(context, ext) 63 | } 64 | irClass.companionObject()?.let(::lower) 65 | } 66 | } 67 | 68 | internal fun T?.followedBy(list: Collection): List { 69 | if (this == null) return list.toList() 70 | val new = ArrayList(list.size + 1) 71 | new.add(this) 72 | new.addAll(list) 73 | return new 74 | } 75 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/ir/util.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("JvmBlockingBridgeUtils") 2 | @file:Suppress("unused") // for public API 3 | 4 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir 5 | 6 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.GeneratedBlockingBridgeStubForResolution 7 | import org.jetbrains.kotlin.backend.common.lower.parents 8 | import org.jetbrains.kotlin.backend.jvm.ir.psiElement 9 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 10 | import org.jetbrains.kotlin.descriptors.Modality 11 | import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI 12 | import org.jetbrains.kotlin.ir.declarations.* 13 | import org.jetbrains.kotlin.ir.expressions.IrConstructorCall 14 | import org.jetbrains.kotlin.ir.util.* 15 | import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull 16 | 17 | /** 18 | * For annotation class 19 | */ 20 | fun IrClass.isJvmBlockingBridge(): Boolean = 21 | symbol.owner.fqNameWhenAvailable?.asString() == RuntimeIntrinsics.JvmBlockingBridgeFqName.asString() 22 | 23 | /** 24 | * Filter by annotation `@JvmBlockingBridge` 25 | */ 26 | fun FunctionDescriptor.isJvmBlockingBridge(): Boolean = annotations.hasAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 27 | 28 | /** 29 | * Filter by annotation `@JvmBlockingBridge` 30 | */ 31 | fun IrFunction.isJvmBlockingBridge(): Boolean = annotations.hasAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 32 | 33 | @OptIn(ObsoleteDescriptorBasedAPI::class) 34 | fun IrFunction.isGeneratedBlockingBridgeStub(): Boolean = 35 | this.descriptor.getUserData(GeneratedBlockingBridgeStubForResolution) == true 36 | 37 | fun IrSimpleFunction.isUserDeclaredFunction(): Boolean { 38 | return originalFunction.psiElement != null 39 | } 40 | 41 | fun IrSimpleFunction.findOverriddenDescriptorsHierarchically(filter: (IrSimpleFunction) -> Boolean): IrSimpleFunction? { 42 | for (override in this.allOverridden(false)) { 43 | if (filter(override)) { 44 | return override 45 | } 46 | val find = override.findOverriddenDescriptorsHierarchically(filter) 47 | if (find != null) return find 48 | } 49 | return null 50 | } 51 | 52 | internal fun IrAnnotationContainer.jvmBlockingBridgeAnnotation(): IrConstructorCall? = 53 | annotations.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 54 | 55 | fun IrFunction.jvmBlockingBridgeAnnotationOnContainingClass(): IrConstructorCall? { 56 | val containingClass = parent 57 | 58 | if (containingClass is IrAnnotationContainer) { 59 | val annotation = containingClass.annotations.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 60 | if (annotation != null) return annotation 61 | } 62 | 63 | if (containingClass is IrClass) { 64 | val file = containingClass.parents.firstIsInstanceOrNull() 65 | val annotation = file?.annotations?.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 66 | if (annotation != null) return annotation 67 | } 68 | 69 | return null 70 | } 71 | 72 | internal val IrFunction.isFinal get() = this is IrSimpleFunction && this.modality == Modality.FINAL 73 | internal val IrFunction.isOpen get() = this is IrSimpleFunction && this.modality == Modality.OPEN 74 | internal val IrFunction.isAbstract get() = this is IrSimpleFunction && this.modality == Modality.ABSTRACT 75 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/resolve/analyzeCapabilityForGeneratingBridges.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.BlockingBridgeAnalyzeResult.* 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 5 | import org.jetbrains.kotlin.backend.common.descriptors.allParameters 6 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 7 | import org.jetbrains.kotlin.descriptors.effectiveVisibility 8 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 9 | import org.jetbrains.kotlin.resolve.BindingContext 10 | import org.jetbrains.kotlin.resolve.descriptorUtil.module 11 | import org.jetbrains.kotlin.resolve.isInlineClass 12 | import org.jetbrains.kotlin.resolve.isInlineClassType 13 | import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmSyntheticAnnotation 14 | 15 | 16 | fun FunctionDescriptor.analyzeCapabilityForGeneratingBridges( 17 | bindingContext: BindingContext, 18 | ext: IBridgeConfiguration, 19 | ): BlockingBridgeAnalyzeResult { 20 | var annotationFromContainingClass = false 21 | 22 | // null iff enableForModule 23 | val jvmBlockingBridgeAnnotation = 24 | jvmBlockingBridgeAnnotation() 25 | ?: jvmBlockingBridgeAnnotationOnContainingDeclaration(bindingContext) 26 | .also { annotationFromContainingClass = true } 27 | ?: kotlin.run { 28 | if (ext.enableForModule) { 29 | null 30 | } else return MissingAnnotationPsi 31 | } 32 | 33 | // null iff enableForModule 34 | val jvmBlockingBridgeAnnotationPsi = 35 | if (jvmBlockingBridgeAnnotation == null) null 36 | else jvmBlockingBridgeAnnotation.findPsi() ?: return MissingAnnotationPsi 37 | 38 | val enableForModule = jvmBlockingBridgeAnnotationPsi == null 39 | 40 | if (enableForModule || annotationFromContainingClass) { 41 | if (this.hasJvmSyntheticAnnotation()) return EnableForModule 42 | } 43 | 44 | // now that the function has @JvmBlockingBridge on self or containing declaration 45 | 46 | fun impl(): BlockingBridgeAnalyzeResult { 47 | // fun must be suspend and applied to member function 48 | if (!isSuspend || name.isSpecial) { 49 | return Inapplicable(jvmBlockingBridgeAnnotationPsi ?: return EnableForModule) 50 | } 51 | 52 | if (isGeneratedBlockingBridgeStub()) { 53 | // @JvmBlockingBridge and @GeneratedBlockingBridge both present 54 | return FromStub 55 | } 56 | 57 | val containingClass = containingClass 58 | if (!visibility.effectiveVisibility(this, true).publicApi) { 59 | // effectively internal api 60 | return RedundantForNonPublicDeclarations(jvmBlockingBridgeAnnotationPsi ?: return EnableForModule) 61 | } 62 | 63 | if (containingClass?.isInlineClass() == true) { 64 | // inside inline class not supported 65 | return InlineClassesNotSupported(jvmBlockingBridgeAnnotationPsi ?: return EnableForModule, 66 | containingClass) 67 | } 68 | 69 | allParameters.firstOrNull { it.type.isInlineClassType() }?.let { param -> 70 | // inline class param not yet supported 71 | return InlineClassesNotSupported( 72 | param.findPsi() ?: jvmBlockingBridgeAnnotationPsi ?: return EnableForModule, param) 73 | } 74 | 75 | if (containingClass?.isInterface() == true) { 76 | if (module.platform?.isJvm8OrHigher() != true) { 77 | // inside interface and JVM under 8 78 | return InterfaceNotSupported(jvmBlockingBridgeAnnotationPsi ?: return EnableForModule) 79 | } 80 | } 81 | 82 | val overridden = 83 | original.findOverriddenDescriptorsHierarchically { 84 | it.analyzeCapabilityForGeneratingBridges(bindingContext, ext).shouldGenerate 85 | } 86 | 87 | if (overridden != null) { 88 | // super function has @ 89 | // generate only if this function has @, or implied from @ on class, which concluded as 'isDeclared' 90 | return OverridesSuper(isUserDeclaredFunction()) 91 | } 92 | 93 | // super function no @ 94 | // this function may has @ or implied from 95 | return if (isUserDeclaredFunction()) { 96 | // explicit 'override' then generate for it. 97 | Allowed 98 | } else { 99 | // implicit override by compiler, don't generate. 100 | BridgeAnnotationFromContainingDeclaration(null) 101 | } 102 | } 103 | 104 | val result = impl() 105 | if (annotationFromContainingClass) { 106 | if (!result.diagnosticPassed) { 107 | return BridgeAnnotationFromContainingDeclaration(result) 108 | } 109 | } 110 | return result 111 | } 112 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/resolve/analyzing.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve 2 | 3 | import com.intellij.psi.PsiElement 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir.RuntimeIntrinsics 5 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeErrors.* 6 | import org.jetbrains.kotlin.config.JvmTarget 7 | import org.jetbrains.kotlin.descriptors.* 8 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 9 | import org.jetbrains.kotlin.diagnostics.Diagnostic 10 | import org.jetbrains.kotlin.load.kotlin.toSourceElement 11 | import org.jetbrains.kotlin.name.FqName 12 | import org.jetbrains.kotlin.platform.TargetPlatform 13 | import org.jetbrains.kotlin.psi.KtFile 14 | import org.jetbrains.kotlin.resolve.BindingContext 15 | import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext 16 | import org.jetbrains.kotlin.resolve.descriptorUtil.parents 17 | import org.jetbrains.kotlin.resolve.source.KotlinSourceElement 18 | import org.jetbrains.kotlin.resolve.source.PsiSourceElement 19 | import org.jetbrains.kotlin.resolve.source.PsiSourceFile 20 | import org.jetbrains.kotlin.resolve.source.getPsi 21 | 22 | 23 | sealed class BlockingBridgeAnalyzeResult( 24 | val diagnosticPassed: Boolean, 25 | val shouldGenerate: Boolean = diagnosticPassed, 26 | ) { 27 | open fun createDiagnostic(): Diagnostic? = null 28 | 29 | object Allowed : BlockingBridgeAnalyzeResult(true) 30 | object MissingAnnotationPsi : BlockingBridgeAnalyzeResult(true, false) 31 | object FromStub : BlockingBridgeAnalyzeResult(true, false) 32 | 33 | object EnableForModule : BridgeAnnotationFromContainingDeclaration(null) 34 | 35 | /** 36 | * Has JvmBlockingBridge annotation on containing declaration, but this function is not capable to have bridge. 37 | * 38 | * @since 1.8 39 | */ 40 | open class BridgeAnnotationFromContainingDeclaration( 41 | val original: BlockingBridgeAnalyzeResult?, 42 | ) : BlockingBridgeAnalyzeResult(true, false) 43 | 44 | class Inapplicable( 45 | private val inspectionTarget: PsiElement, 46 | ) : BlockingBridgeAnalyzeResult(false) { 47 | override fun createDiagnostic(): Diagnostic = INAPPLICABLE_JVM_BLOCKING_BRIDGE.on(inspectionTarget) 48 | } 49 | 50 | /** 51 | * When super member has `@JvmBlockingBridge`, but this doesn't. 52 | */ 53 | class OverridesSuper(shouldGenerate: Boolean) : BlockingBridgeAnalyzeResult(true, shouldGenerate) 54 | 55 | /** 56 | * Below Java 8 57 | */ 58 | class InterfaceNotSupported( 59 | private val inspectionTarget: PsiElement, 60 | ) : BlockingBridgeAnalyzeResult(false) { 61 | override fun createDiagnostic(): Diagnostic = INTERFACE_NOT_SUPPORTED.on(inspectionTarget) 62 | } 63 | 64 | class InlineClassesNotSupported( 65 | /** 66 | * [ParameterDescriptor], [ClassDescriptor] 67 | */ 68 | private val inspectionTarget: PsiElement, 69 | private val causeDeclaration: DeclarationDescriptor, 70 | ) : BlockingBridgeAnalyzeResult(false) { 71 | override fun createDiagnostic(): Diagnostic = 72 | INLINE_CLASSES_NOT_SUPPORTED.on(inspectionTarget, causeDeclaration) 73 | } 74 | 75 | class RedundantForNonPublicDeclarations( 76 | private val inspectionTarget: PsiElement, 77 | ) : BlockingBridgeAnalyzeResult(false, false) { 78 | override fun createDiagnostic(): Diagnostic = 79 | REDUNDANT_JVM_BLOCKING_BRIDGE_ON_NON_PUBLIC_DECLARATIONS.on(inspectionTarget) 80 | } 81 | 82 | } 83 | 84 | fun TargetPlatform.isJvm8OrHigher(): Boolean { 85 | return componentPlatforms 86 | .any { (it.targetPlatformVersion as? JvmTarget ?: JvmTarget.DEFAULT) >= JvmTarget.JVM_1_8 } 87 | } 88 | 89 | fun TargetPlatform.hasJvmComponent(): Boolean { 90 | return componentPlatforms 91 | .any { it.targetPlatformVersion is JvmTarget } 92 | } 93 | 94 | internal fun ClassDescriptor.isInterface(): Boolean = this.kind == ClassKind.INTERFACE 95 | 96 | fun FunctionDescriptor.jvmBlockingBridgeAnnotationOnContainingDeclaration( 97 | bindingContext: BindingContext, 98 | ): AnnotationDescriptor? { 99 | return when (val containingDeclaration = containingDeclaration) { 100 | is ClassDescriptor -> { 101 | // member function 102 | 103 | val ann = containingDeclaration.annotations.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 104 | if (ann != null) return ann 105 | 106 | containingDeclaration.findFileAnnotation(bindingContext, RuntimeIntrinsics.JvmBlockingBridgeFqName) 107 | } 108 | 109 | is PackageFragmentDescriptor -> { 110 | // top-level function 111 | return containingDeclaration.jvmBlockingBridgeAnnotation() 112 | } 113 | 114 | else -> return null 115 | } 116 | } 117 | 118 | fun DeclarationDescriptorWithSource.findFileAnnotation( 119 | bindingContext: BindingContext, 120 | fqName: FqName, 121 | ): AnnotationDescriptor? { 122 | return containingFileAnnotations(bindingContext)?.find { it.fqName == fqName } 123 | } 124 | 125 | // ((((this.containingDeclaration as LazyClassDescriptor).source as KotlinSourceElement).containingFile as PsiSourceFile).psiFile as KtFile).annotationEntries 126 | fun DeclarationDescriptorWithSource.containingFileAnnotations(bindingContext: BindingContext): List? { 127 | val sourceFile = (source as? KotlinSourceElement)?.containingFile as? PsiSourceFile ?: return null 128 | val file = sourceFile.psiFile as? KtFile ?: return null 129 | return file.annotationEntries.mapNotNull { 130 | bindingContext[BindingContext.ANNOTATION, it] 131 | } 132 | } 133 | 134 | internal fun FunctionDescriptor.isUserDeclaredFunction(): Boolean = original.toSourceElement.getPsi() != null 135 | 136 | internal val FunctionDescriptor.containingClass: ClassDescriptor? 137 | get() = this.parents.firstOrNull { it is ClassDescriptor } as ClassDescriptor? 138 | 139 | internal fun DeclarationCheckerContext.report(diagnostic: Diagnostic) = trace.report(diagnostic) 140 | internal fun DeclarationDescriptor.jvmBlockingBridgeAnnotationPsi(): PsiElement? = 141 | annotations.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName)?.findPsi() 142 | 143 | internal fun DeclarationDescriptor.jvmBlockingBridgeAnnotation(): AnnotationDescriptor? = 144 | annotations.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) 145 | 146 | internal fun AnnotationDescriptor.findPsi(): PsiElement? = (source as? PsiSourceElement)?.psi -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/resolve/generateDefaultIfNeeded.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve 2 | 3 | import org.jetbrains.kotlin.codegen.DefaultParameterValueLoader 4 | import org.jetbrains.kotlin.codegen.FunctionCodegen 5 | import org.jetbrains.kotlin.codegen.OwnerKind 6 | import org.jetbrains.kotlin.codegen.context.MethodContext 7 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 8 | import org.jetbrains.kotlin.psi.KtNamedFunction 9 | 10 | 11 | /* 12 | 13 | void generateDefaultIfNeeded( 14 | @NotNull MethodContext owner, 15 | @NotNull FunctionDescriptor functionDescriptor, 16 | @NotNull OwnerKind kind, 17 | @NotNull DefaultParameterValueLoader loadStrategy, 18 | @Nullable KtNamedFunction function 19 | ) 20 | */ 21 | private val GENERATE_DEFAULT_IF_NEEDED = 22 | FunctionCodegen::class.java.getDeclaredMethod( 23 | "generateDefaultIfNeeded", 24 | MethodContext::class.java, 25 | FunctionDescriptor::class.java, 26 | OwnerKind::class.java, 27 | DefaultParameterValueLoader::class.java, 28 | KtNamedFunction::class.java 29 | ).apply { 30 | kotlin.runCatching { isAccessible = true } 31 | } 32 | 33 | internal fun FunctionCodegen.generateDefaultIfNeeded1( 34 | owner: MethodContext, 35 | functionDescriptor: FunctionDescriptor, 36 | kind: OwnerKind, 37 | loadStrategy: DefaultParameterValueLoader, 38 | function: KtNamedFunction?, 39 | ) { 40 | GENERATE_DEFAULT_IF_NEEDED(this, owner, functionDescriptor, kind, loadStrategy, function) 41 | } -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/backend/resolve/util.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir.RuntimeIntrinsics 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.HasJvmBlockingBridgeAnnotation.* 5 | import org.jetbrains.kotlin.codegen.state.md5base64 6 | import org.jetbrains.kotlin.descriptors.* 7 | import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor 8 | import org.jetbrains.kotlin.name.FqName 9 | import org.jetbrains.kotlin.name.Name 10 | import org.jetbrains.kotlin.resolve.BindingContext 11 | import org.jetbrains.kotlin.resolve.annotations.argumentValue 12 | 13 | internal val FunctionDescriptor.jvmName: String? 14 | get() = annotations.findAnnotation(JVM_NAME_FQ_NAME) 15 | ?.argumentValue("name") 16 | ?.value as String? 17 | 18 | internal val FunctionDescriptor.jvmNameOrName: Name 19 | get() = jvmName?.let { Name.identifier(it) } ?: name 20 | 21 | private val JVM_NAME_FQ_NAME = FqName(JvmName::class.qualifiedName!!) 22 | 23 | /** 24 | * For ignoring 25 | */ 26 | object GeneratedBlockingBridgeStubForResolution : CallableDescriptor.UserDataKey 27 | 28 | fun FunctionDescriptor.isGeneratedBlockingBridgeStub(): Boolean = 29 | this.getUserData(GeneratedBlockingBridgeStubForResolution) == true 30 | 31 | fun FunctionDescriptor.findOverriddenDescriptorsHierarchically(filter: (FunctionDescriptor) -> Boolean): FunctionDescriptor? { 32 | val overridden = this.overriddenDescriptors 33 | for (overriddenDescriptor in overridden) { 34 | if (filter(overriddenDescriptor)) { 35 | return overriddenDescriptor 36 | } 37 | } 38 | for (overriddenDescriptor in overridden) { 39 | val got = overriddenDescriptor.findOverriddenDescriptorsHierarchically(filter) 40 | if (got != null) 41 | return got 42 | } 43 | return null 44 | } 45 | 46 | enum class HasJvmBlockingBridgeAnnotation( 47 | val generate: Boolean, 48 | val inlayHints: Boolean = false, 49 | ) { 50 | FROM_FUNCTION(true), 51 | 52 | /** 53 | * @since 1.8 54 | */ 55 | FROM_CONTAINING_DECLARATION(true, true), 56 | 57 | /** 58 | * @since 1.10 59 | */ 60 | ENABLE_FOR_MODULE(true, true), 61 | NONE(false), 62 | } 63 | 64 | fun DeclarationDescriptor.hasJvmBlockingBridgeAnnotation( 65 | bindingContext: BindingContext, 66 | enableForModule: Boolean, 67 | ): HasJvmBlockingBridgeAnnotation { 68 | return when (this) { 69 | is ClassDescriptor -> { 70 | when { 71 | enableForModule -> ENABLE_FOR_MODULE 72 | this.annotations.hasAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName) -> FROM_CONTAINING_DECLARATION 73 | findFileAnnotation( 74 | bindingContext, 75 | RuntimeIntrinsics.JvmBlockingBridgeFqName 76 | ) != null -> FROM_CONTAINING_DECLARATION 77 | else -> NONE 78 | } 79 | } 80 | is FunctionDescriptor -> { 81 | if (this.annotations.hasAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName)) { 82 | FROM_FUNCTION 83 | } else this.containingClass?.hasJvmBlockingBridgeAnnotation(bindingContext, enableForModule) ?: NONE 84 | } 85 | is PackageFragmentDescriptor -> { 86 | if (findFileAnnotation(bindingContext, RuntimeIntrinsics.JvmBlockingBridgeFqName) != null) { 87 | FROM_CONTAINING_DECLARATION 88 | } else NONE 89 | } 90 | else -> NONE 91 | } 92 | } 93 | 94 | internal fun FunctionDescriptor.mangleBridgeLambdaClassname( 95 | parentName: String = this.containingDeclaration.name.identifier, 96 | ): String { 97 | val signature = md5base64(this.computeJvmDescriptor(withReturnType = true, withName = true)) 98 | return "$parentName\$\$$name\$\$bb\$$signature" // clazz$$functionName$$bb$6sv54r 99 | } 100 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/diagnostic/BlockingBridgeDeclarationChecker.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.* 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.HasJvmBlockingBridgeAnnotation.* 5 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeDeclarationChecker.CheckResult.BREAK 6 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeDeclarationChecker.CheckResult.CONTINUE 7 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeErrors.* 8 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 9 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 10 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 11 | import org.jetbrains.kotlin.name.FqName 12 | import org.jetbrains.kotlin.psi.KtClass 13 | import org.jetbrains.kotlin.psi.KtDeclaration 14 | import org.jetbrains.kotlin.psi.KtNamedFunction 15 | import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker 16 | import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext 17 | import org.jetbrains.kotlin.resolve.descriptorUtil.module 18 | import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmSyntheticAnnotation 19 | 20 | open class BlockingBridgeDeclarationChecker( 21 | private val ext: (KtDeclaration) -> IBridgeConfiguration, 22 | ) : DeclarationChecker { 23 | 24 | override fun check( 25 | declaration: KtDeclaration, 26 | descriptor: DeclarationDescriptor, 27 | context: DeclarationCheckerContext, 28 | ) { 29 | when (declaration) { 30 | is KtClass -> { 31 | val annotation = descriptor.jvmBlockingBridgeAnnotationPsi() ?: return 32 | if (!isPluginEnabled(descriptor)) { 33 | context.report(BLOCKING_BRIDGE_PLUGIN_NOT_ENABLED.on(annotation)) 34 | return 35 | } 36 | if (declaration.isInterface()) { 37 | if (descriptor.module.platform?.isJvm8OrHigher() != true) { 38 | // below 8 39 | context.report(INTERFACE_NOT_SUPPORTED.on(annotation)) 40 | return 41 | } 42 | } 43 | } 44 | is KtNamedFunction -> { 45 | when (BREAK) { 46 | checkApplicability(declaration, descriptor, context), 47 | checkJvmSynthetic(declaration, descriptor, context), 48 | -> return 49 | else -> return 50 | } 51 | } 52 | } 53 | } 54 | 55 | enum class CheckResult { 56 | CONTINUE, 57 | BREAK 58 | } 59 | 60 | protected open fun isPluginEnabled( 61 | descriptor: DeclarationDescriptor, 62 | ): Boolean { 63 | return true // in CLI compiler, always enabled 64 | } 65 | 66 | private fun checkApplicability( 67 | declaration: KtDeclaration, 68 | descriptor: DeclarationDescriptor, 69 | context: DeclarationCheckerContext, 70 | ): CheckResult = with(ext(declaration)) { 71 | val inspectionTarget = 72 | when (descriptor.hasJvmBlockingBridgeAnnotation(context.trace.bindingContext, enableForModule)) { 73 | NONE -> return CONTINUE 74 | 75 | FROM_CONTAINING_DECLARATION, 76 | ENABLE_FOR_MODULE, 77 | -> { 78 | // no need to check applicability for inherited from containing class or file or enabled for module. 79 | return CONTINUE 80 | } 81 | 82 | FROM_FUNCTION -> descriptor.jvmBlockingBridgeAnnotationPsi() ?: declaration 83 | } 84 | 85 | if (!isPluginEnabled(descriptor)) { 86 | context.report(BLOCKING_BRIDGE_PLUGIN_NOT_ENABLED.on(inspectionTarget)) 87 | return BREAK 88 | } 89 | 90 | if (descriptor !is FunctionDescriptor) { 91 | context.report(INAPPLICABLE_JVM_BLOCKING_BRIDGE.on(inspectionTarget)) 92 | return BREAK 93 | } 94 | 95 | val result = descriptor.analyzeCapabilityForGeneratingBridges(context.trace.bindingContext, this) 96 | result.createDiagnostic()?.let(context::report) 97 | 98 | if (result is BlockingBridgeAnalyzeResult.BridgeAnnotationFromContainingDeclaration) return BREAK 99 | return CONTINUE 100 | } 101 | 102 | companion object { 103 | 104 | private val JVM_SYNTHETIC = FqName("kotlin.jvm.JvmSynthetic") 105 | } 106 | 107 | private fun checkJvmSynthetic( 108 | declaration: KtDeclaration, 109 | descriptor: DeclarationDescriptor, 110 | context: DeclarationCheckerContext, 111 | ): CheckResult = with(ext(declaration)) { 112 | val inspectionTarget = 113 | when (descriptor.hasJvmBlockingBridgeAnnotation(context.trace.bindingContext, enableForModule)) { 114 | NONE -> return CONTINUE 115 | FROM_FUNCTION -> descriptor.jvmBlockingBridgeAnnotationPsi() 116 | ?: descriptor.annotations.findAnnotation(JVM_SYNTHETIC)?.findPsi() 117 | ?: declaration 118 | FROM_CONTAINING_DECLARATION, ENABLE_FOR_MODULE -> return CONTINUE 119 | } 120 | 121 | if (descriptor.hasJvmSyntheticAnnotation()) { 122 | context.report(REDUNDANT_JVM_BLOCKING_BRIDGE_WITH_JVM_SYNTHETIC.on(inspectionTarget)) 123 | return CONTINUE 124 | } 125 | return CONTINUE 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/diagnostic/BlockingBridgeErrorsRendering.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeErrors.* 4 | import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages 5 | import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap 6 | import org.jetbrains.kotlin.diagnostics.rendering.Renderers 7 | 8 | object BlockingBridgeErrorsRendering : DefaultErrorMessages.Extension { 9 | private val MAP = DiagnosticFactoryToRendererMap("JvmBlockingBridge").apply { 10 | put( 11 | BLOCKING_BRIDGE_PLUGIN_NOT_ENABLED, 12 | "JvmBlockingBridge compiler plugin is not applied to the module, so this annotation would not be processed. " + 13 | "Make sure that you've setup your buildscript correctly and re-import project." 14 | ) 15 | 16 | put( 17 | OVERRIDING_GENERATED_BLOCKING_BRIDGE, 18 | "Overriding generated JvmBlockingBridge: ''{0}''.", 19 | Renderers.TO_STRING 20 | ) 21 | 22 | put( 23 | REDUNDANT_JVM_BLOCKING_BRIDGE_ON_NON_PUBLIC_DECLARATIONS, 24 | "@JvmBlockingBridge is redundant on private declarations, as generated bridges are also private and can't be resolved from Java." 25 | ) 26 | 27 | put( 28 | REDUNDANT_JVM_BLOCKING_BRIDGE_WITH_JVM_SYNTHETIC, 29 | "@JvmBlockingBridge is redundant on @JvmSynthetic declarations, as generated bridges are also synthetic and can't be resolved from Java." 30 | ) 31 | 32 | put( 33 | INAPPLICABLE_JVM_BLOCKING_BRIDGE, 34 | "@JvmBlockingBridge is not applicable on this declaration." 35 | ) 36 | 37 | put( 38 | INLINE_CLASSES_NOT_SUPPORTED, 39 | "Inline class is not supported for ''{0}''.", 40 | Renderers.DECLARATION_NAME_WITH_KIND 41 | ) 42 | 43 | put( 44 | INTERFACE_NOT_SUPPORTED, 45 | "Interface is not supported for jvm target lower than 8." 46 | ) 47 | 48 | put( 49 | TOP_LEVEL_FUNCTIONS_NOT_SUPPORTED, 50 | "Top-level functions are not yet supported with the legacy JVM compiler backend." 51 | ) 52 | } 53 | 54 | override fun getMap() = MAP 55 | } 56 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/extensions/BridgeCodegenCliExtension.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.extensions 2 | 3 | data class BridgeConfigurationImpl( 4 | override val enableForModule: Boolean, 5 | ) : IBridgeConfiguration 6 | 7 | interface IBridgeConfiguration { 8 | val enableForModule: Boolean 9 | 10 | companion object { 11 | val Default = object : IBridgeConfiguration { 12 | override val enableForModule: Boolean get() = false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/extensions/BridgeCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.extensions 2 | 3 | import com.google.auto.service.AutoService 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.JvmBlockingBridgeCompilerConfigurationKeys.ENABLE_FOR_MODULE 5 | import org.jetbrains.kotlin.compiler.plugin.* 6 | import org.jetbrains.kotlin.config.CompilerConfiguration 7 | 8 | @OptIn(ExperimentalCompilerApi::class) 9 | @AutoService(CommandLineProcessor::class) 10 | class BridgeCommandLineProcessor : CommandLineProcessor { 11 | companion object { 12 | const val COMPILER_PLUGIN_ID: String = "kotlin-jvm-blocking-bridge" 13 | 14 | val OPTION_ENABLE_FOR_MODULE: CliOption = CliOption( 15 | ENABLE_FOR_MODULE.name, 16 | "", 17 | "Generate blocking bridges for all effectively public suspend functions in the module where possible." 18 | ) 19 | } 20 | 21 | override val pluginId: String = COMPILER_PLUGIN_ID 22 | override val pluginOptions: Collection = listOf(OPTION_ENABLE_FOR_MODULE) 23 | 24 | override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) { 25 | when (option) { 26 | OPTION_ENABLE_FOR_MODULE -> configuration.put(ENABLE_FOR_MODULE, value) 27 | else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/extensions/BridgeComponentRegistrar.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.extensions 2 | 3 | import com.google.auto.service.AutoService 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeDeclarationChecker 5 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 6 | import org.jetbrains.kotlin.cli.common.toBooleanLenient 7 | import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar 8 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 9 | import org.jetbrains.kotlin.config.CompilerConfiguration 10 | import org.jetbrains.kotlin.container.StorageComponentContainer 11 | import org.jetbrains.kotlin.container.useInstance 12 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 13 | import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor 14 | import org.jetbrains.kotlin.platform.TargetPlatform 15 | 16 | @OptIn(ExperimentalCompilerApi::class) 17 | @AutoService(CompilerPluginRegistrar::class) 18 | @Suppress("unused") 19 | open class BridgeComponentRegistrar @JvmOverloads constructor( 20 | private val overrideConfigurations: CompilerConfiguration? = null, 21 | ) : CompilerPluginRegistrar() { 22 | override val supportsK2: Boolean get() = true 23 | 24 | override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { 25 | // if (configuratio[KEY_ENABLED] == false) { 26 | // return 27 | // } 28 | 29 | //SyntheticResolveExtension.registerExtension(project, JvmBlockingBridgeResolveExtension()) 30 | 31 | val actualConfiguration = overrideConfigurations ?: configuration 32 | 33 | val ext = actualConfiguration.createBridgeConfig() 34 | 35 | // println("actualConfiguration.toString(): $actualConfiguration") 36 | 37 | StorageComponentContainerContributor.registerExtension(object : StorageComponentContainerContributor { 38 | override fun registerModuleComponents( 39 | container: StorageComponentContainer, 40 | platform: TargetPlatform, 41 | moduleDescriptor: ModuleDescriptor, 42 | ) { 43 | container.useInstance( 44 | BlockingBridgeDeclarationChecker { ext } 45 | ) 46 | } 47 | }) 48 | IrGenerationExtension.registerExtension(JvmBlockingBridgeIrGenerationExtension(ext)) 49 | } 50 | } 51 | 52 | fun CompilerConfiguration.createBridgeConfig(): BridgeConfigurationImpl { 53 | val actualConfiguration = this 54 | 55 | val enableForModule = 56 | actualConfiguration[me.him188.kotlin.jvm.blocking.bridge.compiler.JvmBlockingBridgeCompilerConfigurationKeys.ENABLE_FOR_MODULE] 57 | ?.toBooleanLenient() 58 | ?: false 59 | 60 | return BridgeConfigurationImpl(enableForModule) 61 | } 62 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/compiler/extensions/BridgeIrGenerationExtension.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.compiler.extensions 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir.JvmBlockingBridgeClassLoweringPass 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir.JvmBlockingBridgeFileLoweringPass 5 | import org.jetbrains.kotlin.backend.common.ClassLoweringPass 6 | import org.jetbrains.kotlin.backend.common.FileLoweringPass 7 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 8 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 9 | import org.jetbrains.kotlin.ir.IrElement 10 | import org.jetbrains.kotlin.ir.declarations.IrClass 11 | import org.jetbrains.kotlin.ir.declarations.IrFile 12 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 13 | import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid 14 | import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid 15 | import org.jetbrains.kotlin.ir.visitors.acceptVoid 16 | import org.jetbrains.kotlin.platform.jvm.isJvm 17 | import org.jetbrains.kotlin.platform.konan.isNative 18 | 19 | /** 20 | * For IR backend. 21 | */ 22 | // @AutoService(IrGenerationExtension::class) 23 | open class JvmBlockingBridgeIrGenerationExtension( 24 | private val ext: IBridgeConfiguration, 25 | ) : IrGenerationExtension { 26 | override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { 27 | if (!moduleFragment.descriptor.platform!!.isJvm()) { 28 | return 29 | } 30 | for (file in moduleFragment.files) { 31 | JvmBlockingBridgeClassLoweringPass(pluginContext, ext).runOnFileInOrder(file) 32 | JvmBlockingBridgeFileLoweringPass(pluginContext, ext).runOnFileInOrder(file) 33 | } 34 | } 35 | } 36 | 37 | internal fun ClassLoweringPass.runOnFileInOrder(irFile: IrFile) { 38 | irFile.acceptVoid(object : IrElementVisitorVoid { 39 | override fun visitElement(element: IrElement) { 40 | element.acceptChildrenVoid(this) 41 | } 42 | 43 | override fun visitClass(declaration: IrClass) { 44 | lower(declaration) // lower bridge before lowering suspend 45 | } 46 | }) 47 | } 48 | 49 | internal fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) { 50 | irFile.acceptVoid(object : IrElementVisitorVoid { 51 | override fun visitElement(element: IrElement) { 52 | element.acceptChildrenVoid(this) 53 | } 54 | 55 | override fun visitFile(declaration: IrFile) { 56 | lower(declaration) 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/AbiAnnotationsTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RemoveRedundantBackticks", "RedundantSuspendModifier") 2 | 3 | package compiler 4 | 5 | import assertHasFunction 6 | import assertNoFunction 7 | import org.junit.jupiter.api.Test 8 | import runFunction 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertFailsWith 11 | 12 | 13 | internal class AbiAnnotationsTest : AbstractCompilerTest() { 14 | 15 | @Test 16 | fun `exceptions`() = testJvmCompile( 17 | """ 18 | object TestData { 19 | @Throws(java.io.IOException::class) 20 | @JvmBlockingBridge 21 | suspend fun test() {} 22 | } 23 | """, noMain = true 24 | ) { 25 | classLoader.loadClass("TestData").getMethod("test").run { 26 | assertEquals("java.io.IOException", this.exceptionTypes.single().canonicalName) 27 | } 28 | } 29 | 30 | @Test 31 | fun `jvm overloads`() = testJvmCompile( 32 | """ 33 | class TestData { 34 | @JvmOverloads 35 | @JvmBlockingBridge 36 | suspend fun test(a: String = "") {} 37 | } 38 | """, noMain = true 39 | ) { 40 | classLoader.loadClass("TestData").run { 41 | assertHasFunction("test", String::class.java) 42 | assertNoFunction("test", String::class.java) 43 | assertHasFunction("test") 44 | 45 | this.getConstructor().newInstance().runFunction("test", "") 46 | this.getConstructor().newInstance().runFunction("test") 47 | } 48 | } 49 | 50 | @Test 51 | fun `no jvm overloads`() = testJvmCompile( 52 | """ 53 | object TestData { 54 | @JvmBlockingBridge 55 | suspend fun test(a: String = "") {} 56 | } 57 | """, noMain = true 58 | ) { 59 | classLoader.loadClass("TestData").run { 60 | getMethod("test", String::class.java) 61 | assertFailsWith { getMethod("test") } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/AbstractCompilerTest.kt: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import com.tschuchort.compiletesting.KotlinCompilation 4 | import com.tschuchort.compiletesting.SourceFile 5 | import createInstanceOrNull 6 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.BridgeComponentRegistrar 7 | import org.intellij.lang.annotations.Language 8 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 9 | import org.jetbrains.kotlin.config.CompilerConfiguration 10 | import org.jetbrains.kotlin.config.JvmTarget 11 | import org.jetbrains.kotlin.descriptors.Modality 12 | import org.jetbrains.kotlin.descriptors.Visibilities 13 | import org.jetbrains.kotlin.descriptors.Visibility 14 | import java.io.File 15 | import java.lang.reflect.Method 16 | import java.lang.reflect.Modifier 17 | import java.util.* 18 | import kotlin.reflect.full.companionObjectInstance 19 | import kotlin.test.assertEquals 20 | 21 | internal abstract class AbstractCompilerTest { 22 | protected open val overrideCompilerConfiguration: CompilerConfiguration? = null 23 | 24 | companion object { 25 | const val FILE_SPLITTER = "-------------------------------------" 26 | } 27 | 28 | 29 | fun compile( 30 | @Language("kt") 31 | source: String, 32 | jvmTarget: JvmTarget = JvmTarget.JVM_1_8, 33 | overrideCompilerConfiguration: CompilerConfiguration? = this.overrideCompilerConfiguration, 34 | config: KotlinCompilation.() -> Unit = {}, 35 | ) = compile( 36 | source, 37 | null, 38 | jvmTarget, 39 | overrideCompilerConfiguration = overrideCompilerConfiguration, 40 | config = config 41 | ) 42 | 43 | 44 | @OptIn(ExperimentalCompilerApi::class) 45 | fun compile( 46 | @Language("kt") 47 | sources: String, 48 | @Language("java") 49 | java: String? = null, 50 | jvmTarget: JvmTarget = JvmTarget.JVM_1_8, 51 | overrideCompilerConfiguration: CompilerConfiguration? = this.overrideCompilerConfiguration, 52 | config: KotlinCompilation.() -> Unit = {}, 53 | ): KotlinCompilation.Result { 54 | val intrinsicImports = listOf( 55 | "import kotlin.test.*", 56 | "import JvmBlockingBridge" 57 | ) 58 | 59 | val kotlinSources = sources.split(FILE_SPLITTER).mapIndexed { index, source -> 60 | when { 61 | source.trim().startsWith("package") -> { 62 | SourceFile.kotlin("TestData${index}.kt", run { 63 | source.trimIndent().lines().mapTo(LinkedList()) { it } 64 | .apply { addAll(1, intrinsicImports) } 65 | .joinToString("\n") 66 | }) 67 | } 68 | 69 | source.trim().startsWith("@file:") -> { 70 | SourceFile.kotlin("TestData${index}.kt", run { 71 | source.trim().trimIndent().lines().mapTo(LinkedList()) { it } 72 | .apply { addAll(1, intrinsicImports) } 73 | .joinToString("\n") 74 | }) 75 | } 76 | 77 | else -> { 78 | SourceFile.kotlin( 79 | name = "TestData${index}.kt", 80 | contents = "${intrinsicImports.joinToString("\n")}\n${source.trimIndent()}" 81 | ) 82 | } 83 | } 84 | } 85 | 86 | 87 | return KotlinCompilation().apply { 88 | this.sources = listOfNotNull( 89 | *kotlinSources.toTypedArray(), 90 | java?.let { javaSource -> 91 | SourceFile.java( 92 | Regex("""class\s*(.*?)\s*\{""").find(javaSource)!!.groupValues[1].let { "$it.java" }, 93 | javaSource 94 | ) 95 | } 96 | ) 97 | 98 | compilerPluginRegistrars = listOf(BridgeComponentRegistrar(overrideCompilerConfiguration)) 99 | verbose = false 100 | 101 | this.jvmTarget = jvmTarget.description 102 | 103 | workingDir = File("testCompileOutput").apply { 104 | this.walk().forEach { it.delete() } 105 | mkdir() 106 | } 107 | 108 | useIR = true 109 | 110 | inheritClassPath = true 111 | messageOutputStream = System.out 112 | 113 | config() 114 | }.compile().also { result -> 115 | assert(result.exitCode == KotlinCompilation.ExitCode.OK) { 116 | "Test data compilation failed." 117 | } 118 | } 119 | } 120 | 121 | 122 | fun testJvmCompile( 123 | @Language("kt") 124 | kt: String, 125 | @Language("java") 126 | java: String? = null, 127 | noMain: Boolean = false, 128 | jvmTarget: JvmTarget = JvmTarget.JVM_1_8, 129 | overrideCompilerConfiguration: CompilerConfiguration? = this.overrideCompilerConfiguration, 130 | config: KotlinCompilation.() -> Unit = {}, 131 | block: KotlinCompilation.Result.() -> Unit = {}, 132 | ) { 133 | val result = 134 | compile( 135 | kt, 136 | java, 137 | jvmTarget, 138 | overrideCompilerConfiguration = overrideCompilerConfiguration, 139 | config = config 140 | ) 141 | 142 | if (!noMain) { 143 | val test = result.classLoader.loadClass("TestData") 144 | assertEquals( 145 | "OK", 146 | listOfNotNull( 147 | test.kotlin.objectInstance, 148 | test.kotlin.companionObjectInstance, 149 | test.kotlin.createInstanceOrNull() 150 | ).associateWith { obj -> 151 | obj::class.java.methods.find { it.name == "main" } 152 | }.entries.find { it.value != null }?.let { (instance, method) -> 153 | method!!.invoke(instance) 154 | } as String? ?: error("Cannot find a `main`")) 155 | } 156 | block(result) 157 | } 158 | 159 | 160 | internal val Method.visibility: Visibility 161 | get() = when { 162 | Modifier.isPublic(this.modifiers) -> Visibilities.Public 163 | Modifier.isPrivate(this.modifiers) -> Visibilities.Private 164 | Modifier.isProtected(this.modifiers) -> Visibilities.Protected 165 | else -> Visibilities.PrivateToThis 166 | } 167 | 168 | internal val Method.modality: Modality 169 | get() = when { 170 | Modifier.isFinal(this.modifiers) -> Modality.FINAL 171 | Modifier.isAbstract(this.modifiers) -> Modality.ABSTRACT 172 | else -> Modality.OPEN 173 | } 174 | 175 | 176 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/BasicsTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RemoveRedundantBackticks", "RedundantSuspendModifier", "MainFunctionReturnUnit") 2 | 3 | package compiler 4 | 5 | import org.junit.jupiter.api.Test 6 | import kotlin.test.assertEquals 7 | 8 | internal class BasicsTest : AbstractCompilerTest() { 9 | @Test 10 | fun `topLevel`() = testJvmCompile( 11 | """ 12 | @JvmBlockingBridge 13 | suspend fun test() = "OK" 14 | """, noMain = true 15 | ) { 16 | assertEquals("OK", classLoader.loadClass("TestData0Kt").getDeclaredMethod("test").invoke(null)) 17 | } 18 | 19 | @Test 20 | fun `simple function in object`() = testJvmCompile( 21 | """ 22 | import java.lang.reflect.Modifier 23 | object TestData { 24 | @JvmBlockingBridge 25 | suspend fun test() {} 26 | 27 | fun main(): String { 28 | this.runFunction("test") 29 | return "OK" 30 | } 31 | } 32 | """, 33 | """ 34 | public class J { 35 | public void j() { 36 | TestData.INSTANCE.test(); 37 | } 38 | } 39 | """ 40 | ) 41 | 42 | @Test 43 | fun `function with many param in object`() = testJvmCompile( 44 | """ 45 | object TestData { 46 | @JvmBlockingBridge 47 | suspend fun test(arg1: String, arg2: String, arg3: String): String{ 48 | assertEquals("KO", arg2) 49 | assertEquals("OO", arg3) 50 | return arg1 51 | } 52 | 53 | fun main(): String = this.runFunction("test", "OK", "KO", "OO") 54 | } 55 | """ 56 | ) 57 | 58 | @Test 59 | fun `function with receiver in object`() = testJvmCompile( 60 | """ 61 | object TestData { 62 | @JvmBlockingBridge 63 | suspend fun String.test(arg1: String): String{ 64 | assertEquals("aaa", this) 65 | return arg1 66 | } 67 | 68 | fun main(): String = this.runFunction("test", "aaa", "OK") 69 | } 70 | """ 71 | ) 72 | 73 | @Test 74 | fun `function with primitives2 in object`() = testJvmCompile( 75 | """ 76 | object TestData { 77 | @JvmBlockingBridge 78 | suspend fun test(int: Int, float: Float, double: Double, char: Char, boolean: Boolean, short: Short): String{ 79 | assertEquals(123, int) 80 | assertEquals(123f, float) 81 | assertEquals(123.0, double) 82 | assertEquals('1', char) 83 | assertEquals(true, boolean) 84 | assertEquals(123, short) 85 | return "OK" 86 | } 87 | 88 | fun main(): String = this.runFunction("test", 123, 123f, 123.0, '1', true, 123.toShort()) 89 | } 90 | """ 91 | ) 92 | 93 | @Test 94 | fun `function in class`() = testJvmCompile( 95 | """ 96 | abstract class SuperClass { 97 | @JvmBlockingBridge 98 | suspend fun test(value: String): String { 99 | assertEquals("v", value) 100 | return "OK" 101 | } 102 | } 103 | 104 | object TestData : SuperClass() { 105 | fun main(): String = this.runFunction("test", "v") 106 | } 107 | """ 108 | ) 109 | 110 | @Test 111 | fun `jvm name`() = testJvmCompile( 112 | """ 113 | object TestData { 114 | @kotlin.jvm.JvmName("test") 115 | @JvmBlockingBridge 116 | suspend fun String.test2(arg: String): String{ 117 | assertEquals("receiver", this) 118 | assertEquals("p0", arg) 119 | return "OK" 120 | } 121 | 122 | fun main(): String = this.runFunction("test", "receiver", "p0") 123 | } 124 | """ 125 | ) 126 | 127 | @Test 128 | fun `abstract`() = testJvmCompile( 129 | """ 130 | 131 | abstract class Abstract { 132 | abstract suspend fun test(): String 133 | } 134 | object TestData : Abstract() { 135 | @JvmBlockingBridge 136 | override suspend fun test() = "OK" 137 | 138 | fun main(): String = TestData.runFunction("test") 139 | } 140 | """ 141 | ) 142 | 143 | @Test 144 | fun `mangling`() = testJvmCompile( 145 | """ 146 | object TestData { 147 | @JvmBlockingBridge 148 | suspend fun test() = "OK" 149 | @JvmBlockingBridge 150 | suspend fun test(s: String) = "OK" 151 | 152 | fun main(): String = TestData.runFunction("test") 153 | } 154 | """ 155 | ) 156 | } 157 | -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/BridgeAnnotationOnFileTest.kt: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import assertHasFunction 4 | import createInstance 5 | import org.junit.jupiter.api.Test 6 | import runFunction 7 | import kotlin.coroutines.Continuation 8 | import kotlin.test.assertFailsWith 9 | 10 | internal class BridgeAnnotationOnFileTest : AbstractCompilerTest() { 11 | 12 | @Test 13 | fun `suspend gen`() = testJvmCompile( 14 | """ 15 | @file:JvmBlockingBridge 16 | object TestData { 17 | suspend fun test() {} 18 | } 19 | """, noMain = true 20 | ) { 21 | classLoader.loadClass("TestData").kotlin.objectInstance!!.runFunction("test") 22 | } 23 | 24 | @Test 25 | fun `non suspend should be ok`() = testJvmCompile( 26 | """ 27 | @file:JvmBlockingBridge 28 | object TestData { 29 | fun test() {} 30 | } 31 | """, noMain = true 32 | ) 33 | 34 | @Test 35 | fun `no inspection even inapplicable`() = testJvmCompile( 36 | """ 37 | @file:JvmBlockingBridge 38 | object TestData { 39 | @JvmSynthetic 40 | suspend fun test() {} 41 | 42 | private suspend fun test2() {} 43 | } 44 | """, noMain = true 45 | ) { 46 | classLoader.loadClass("TestData") 47 | } 48 | 49 | 50 | /////////////////////////////////////////////////////////////////////////// 51 | // inheritance 52 | // copied from InheritanceTest and changed places of annotations 53 | /////////////////////////////////////////////////////////////////////////// 54 | 55 | @Test 56 | fun `bridge for abstract`() = testJvmCompile( 57 | """ 58 | @file:JvmBlockingBridge 59 | abstract class Abstract { 60 | abstract suspend fun test(): String 61 | } 62 | 63 | $FILE_SPLITTER 64 | 65 | object TestData : Abstract() { 66 | override suspend fun test() = "OK" 67 | 68 | fun main(): String = TestData.runFunction("test") 69 | } 70 | """.trimIndent() 71 | ) { 72 | assertFailsWith { 73 | classLoader.loadClass("Abstract").run { 74 | assertHasFunction("test") 75 | } 76 | classLoader.loadClass("TestData").getDeclaredMethod("test") 77 | } 78 | } 79 | 80 | @Test 81 | fun `bridge for overridden`() = testJvmCompile( 82 | """ 83 | abstract class Abstract { 84 | abstract suspend fun test(): String 85 | } 86 | 87 | $FILE_SPLITTER 88 | 89 | @file:JvmBlockingBridge 90 | object TestData : Abstract() { 91 | override suspend fun test() = "OK" 92 | 93 | fun main(): String = TestData.runFunction("test") 94 | } 95 | """.trimIndent() 96 | ) { 97 | classLoader.loadClass("TestData").run { 98 | assertHasFunction("test") 99 | } 100 | } 101 | 102 | @Test 103 | fun `bridge for interface overriding`() = testJvmCompile( 104 | """ 105 | interface Interface3 { 106 | suspend fun test(): String 107 | } 108 | 109 | $FILE_SPLITTER 110 | @file:JvmBlockingBridge 111 | object TestData : Interface3 { 112 | override suspend fun test() = "OK" 113 | } 114 | """.trimIndent(), noMain = true 115 | ) { 116 | classLoader.loadClass("TestData").run { 117 | assertHasFunction("test") 118 | createInstance().run { 119 | runFunction("test") 120 | } 121 | } 122 | } 123 | 124 | @Test 125 | fun `bridge for interface inheritance`() = testJvmCompile( 126 | """ 127 | @file:JvmBlockingBridge 128 | interface Interface2 { 129 | suspend fun test(): String 130 | } 131 | 132 | $FILE_SPLITTER 133 | object TestData : Interface2 { 134 | override suspend fun test() = "OK" 135 | 136 | fun main(): String = TestData.runFunction("test") 137 | } 138 | """.trimIndent() 139 | ) { 140 | classLoader.loadClass("Interface2").run { 141 | assertHasFunction("test") 142 | assertHasFunction("test", Continuation::class.java) 143 | } 144 | classLoader.loadClass("TestData").run { 145 | assertHasFunction("test") 146 | assertHasFunction("test", Continuation::class.java) 147 | } 148 | assertFailsWith { 149 | classLoader.loadClass("TestData").getDeclaredMethod("test") 150 | } 151 | } 152 | 153 | @Test 154 | fun `interface codegen`() = testJvmCompile( 155 | """ 156 | @JvmBlockingBridge 157 | interface Interface { 158 | suspend fun test(): String 159 | } 160 | """, noMain = true 161 | ) { 162 | classLoader.loadClass("Interface").run { 163 | assertHasFunction("test") 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/BridgeForModule.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RedundantSuspendModifier") 2 | 3 | package compiler 4 | 5 | import assertHasFunction 6 | import assertNoFunction 7 | import createInstance 8 | import org.jetbrains.kotlin.config.CompilerConfiguration 9 | import org.junit.jupiter.api.Test 10 | import runFunction 11 | import kotlin.test.assertEquals 12 | 13 | 14 | internal class BridgeForModule : AbstractCompilerTest() { 15 | override val overrideCompilerConfiguration: CompilerConfiguration = CompilerConfiguration().apply { 16 | put( 17 | me.him188.kotlin.jvm.blocking.bridge.compiler.JvmBlockingBridgeCompilerConfigurationKeys.ENABLE_FOR_MODULE, 18 | true.toString() 19 | ) 20 | } 21 | 22 | @Test 23 | fun simple() = testJvmCompile( 24 | """ 25 | object TestData { 26 | suspend fun test() = "OK" 27 | } 28 | """, noMain = true 29 | ) { 30 | classLoader.loadClass("TestData").apply { 31 | assertHasFunction("test") 32 | assertEquals("OK", createInstance().runFunction("test")) 33 | } 34 | } 35 | 36 | @Test 37 | fun static() = testJvmCompile( 38 | """ 39 | object TestData { 40 | @JvmStatic 41 | suspend fun test(arg: String) { // returns Unit 42 | } 43 | 44 | fun main(): String { 45 | Class.forName("TestData").assertHasFunction("test", String::class.java) 46 | return "OK" 47 | } 48 | } 49 | """ 50 | ) 51 | 52 | @Test 53 | fun inheritance() = testJvmCompile( 54 | """ 55 | interface A { 56 | suspend fun test(arg: String) {} 57 | } 58 | interface B : A { 59 | override suspend fun test(arg: String) {} 60 | } 61 | """, noMain = true 62 | ) { 63 | classLoader.loadClass("A").assertHasFunction("test", String::class, declaredOnly = true) 64 | classLoader.loadClass("B").assertHasFunction("test", String::class, declaredOnly = true) 65 | } 66 | 67 | @Test 68 | fun `static companion`() = testJvmCompile( 69 | """ 70 | class TestData { 71 | companion object { 72 | @JvmStatic 73 | suspend fun test(arg: String) { // returns Unit 74 | } 75 | 76 | @JvmStatic 77 | fun main(): String { 78 | Class.forName("TestData").assertHasFunction("test", String::class.java) 79 | return "OK" 80 | } 81 | } 82 | } 83 | """ 84 | ) 85 | 86 | @Test 87 | fun `synthetic in static companion`() = testJvmCompile( 88 | """ 89 | class TestData { 90 | companion object { 91 | @JvmSynthetic 92 | @JvmStatic 93 | suspend fun test() { 94 | } 95 | } 96 | } 97 | """, noMain = true 98 | ) { 99 | classLoader.loadClass("TestData").run { 100 | assertNoFunction("test") 101 | assertNoFunction("test") 102 | } 103 | classLoader.loadClass("TestData\$Companion").run { 104 | assertNoFunction("test") 105 | assertNoFunction("test") 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/InheritanceTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RemoveRedundantBackticks", "RedundantSuspendModifier", "MainFunctionReturnUnit") 2 | 3 | package compiler 4 | 5 | import assertHasFunction 6 | import assertNoFunction 7 | import createInstance 8 | import org.junit.jupiter.api.Test 9 | import runFunction 10 | import kotlin.coroutines.Continuation 11 | 12 | internal class InheritanceTest : AbstractCompilerTest() { 13 | @Test 14 | fun `bridge for abstract`() = testJvmCompile( 15 | """ 16 | abstract class Abstract { 17 | @JvmBlockingBridge 18 | abstract suspend fun test(): String 19 | } 20 | object TestData : Abstract() { 21 | override suspend fun test() = "OK" 22 | 23 | fun main(): String = TestData.runFunction("test") 24 | } 25 | """ 26 | ) { 27 | classLoader.loadClass("Abstract").run { 28 | assertHasFunction("test") 29 | } 30 | classLoader.loadClass("TestData").assertNoFunction("test", declaredOnly = true) 31 | } 32 | 33 | @Test 34 | fun `bridge for open fun`() = testJvmCompile( 35 | """ 36 | sealed class S1 { 37 | @JvmBlockingBridge 38 | open suspend fun test(): String { 39 | return "OK" 40 | } 41 | } 42 | sealed class S2 : S1() 43 | object TestData : S2() { 44 | fun main(): String = TestData.runFunction("test") 45 | } 46 | """ 47 | ) { 48 | classLoader.loadClass("S1").run { 49 | assertHasFunction("test") 50 | } 51 | classLoader.loadClass("S2").run { 52 | assertNoFunction("test", declaredOnly = true) 53 | } 54 | classLoader.loadClass("TestData").run { 55 | assertNoFunction("test", declaredOnly = true) 56 | } 57 | } 58 | 59 | @Test 60 | fun `bridge for overridden`() = testJvmCompile( 61 | """ 62 | abstract class Abstract { 63 | abstract suspend fun test(): String 64 | } 65 | object TestData : Abstract() { 66 | @JvmBlockingBridge 67 | override suspend fun test() = "OK" 68 | 69 | fun main(): String = TestData.runFunction("test") 70 | } 71 | """ 72 | ) { 73 | classLoader.loadClass("TestData").run { 74 | assertHasFunction("test") 75 | } 76 | } 77 | 78 | @Test 79 | fun `bridge for interface overriding`() = testJvmCompile( 80 | """ 81 | interface Interface3 { 82 | suspend fun test(): String 83 | } 84 | object TestData : Interface3 { 85 | @JvmBlockingBridge 86 | override suspend fun test() = "OK" 87 | 88 | fun main(): String = TestData.runFunction("test") 89 | } 90 | """ 91 | ) { 92 | classLoader.loadClass("TestData").run { 93 | assertHasFunction("test") 94 | createInstance().run { 95 | runFunction("test") 96 | } 97 | } 98 | } 99 | 100 | @Test 101 | fun `bridge for interface inheritance`() = testJvmCompile( 102 | """ 103 | interface Interface2 { 104 | @JvmBlockingBridge 105 | suspend fun test(): String 106 | } 107 | object TestData : Interface2 { 108 | override suspend fun test() = "OK" 109 | 110 | fun main(): String = TestData.runFunction("test") 111 | } 112 | """ 113 | ) { 114 | classLoader.loadClass("Interface2").run { 115 | assertHasFunction("test") 116 | assertHasFunction("test", Continuation::class.java) 117 | } 118 | classLoader.loadClass("TestData").run { 119 | assertHasFunction("test") 120 | assertHasFunction("test", Continuation::class.java) 121 | } 122 | classLoader.loadClass("TestData").assertNoFunction("test", declaredOnly = true) 123 | } 124 | 125 | @Test 126 | fun `interface override`() = testJvmCompile( 127 | """ 128 | interface Interface2 { 129 | @JvmBlockingBridge 130 | suspend fun test(): String 131 | } 132 | interface TestData : Interface2 { 133 | @JvmBlockingBridge 134 | override suspend fun test() = "OK" 135 | } 136 | """, noMain = true 137 | ) { 138 | classLoader.loadClass("Interface2").run { 139 | assertHasFunction("test", declaredOnly = true) 140 | assertHasFunction("test", Continuation::class.java, declaredOnly = true) 141 | } 142 | classLoader.loadClass("TestData").run { 143 | assertHasFunction("test", declaredOnly = true) 144 | assertHasFunction("test", Continuation::class.java, declaredOnly = true) 145 | } 146 | } 147 | 148 | @Test 149 | fun `gen bridge for overridden`() = testJvmCompile( 150 | """ 151 | interface Interface2 { 152 | @JvmBlockingBridge 153 | suspend fun test(): String 154 | } 155 | object TestData : Interface2 { 156 | @JvmBlockingBridge 157 | override suspend fun test() = "OK" 158 | 159 | fun main(): String = TestData.runFunction("test") 160 | } 161 | """ 162 | ) { 163 | classLoader.loadClass("Interface2").run { 164 | assertHasFunction("test") 165 | assertHasFunction("test", Continuation::class.java) 166 | } 167 | classLoader.loadClass("TestData").run { 168 | assertHasFunction("test") 169 | assertHasFunction("test", Continuation::class.java) 170 | } 171 | classLoader.loadClass("TestData").run { 172 | assertHasFunction("test", declaredOnly = true) 173 | assertHasFunction("test", Continuation::class.java, declaredOnly = true) 174 | } 175 | } 176 | 177 | @Test 178 | fun `interface codegen`() = testJvmCompile( 179 | """ 180 | interface Interface { 181 | @JvmBlockingBridge 182 | suspend fun test(): String 183 | } 184 | """, noMain = true 185 | ) { 186 | classLoader.loadClass("Interface").run { 187 | assertHasFunction("test") 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/StaticTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RedundantSuspendModifier") 2 | 3 | package compiler 4 | 5 | import assertHasFunction 6 | import org.junit.jupiter.api.Test 7 | import runFunction 8 | import runStaticFunction 9 | import kotlin.reflect.full.companionObject 10 | import kotlin.reflect.full.companionObjectInstance 11 | 12 | internal class StaticTest : AbstractCompilerTest() { 13 | 14 | @Test 15 | fun static() = testJvmCompile( 16 | """ 17 | object TestData { 18 | @JvmStatic 19 | @JvmBlockingBridge 20 | suspend fun String.test(arg: String): String{ 21 | assertEquals("receiver", this) 22 | assertEquals("p0", arg) 23 | return "OK" 24 | } 25 | 26 | fun main(): String = Class.forName("TestData").runStaticFunction("test", "receiver", "p0") 27 | } 28 | """ 29 | ) 30 | 31 | @Test 32 | fun `static function in class`() = testJvmCompile( 33 | """ 34 | class TestData { 35 | companion object { 36 | @JvmStatic 37 | @JvmBlockingBridge 38 | suspend fun String.test(arg: String): String{ 39 | assertEquals("receiver", this) 40 | assertEquals("p0", arg) 41 | return "OK" 42 | } 43 | } 44 | 45 | fun main(): String = Class.forName("TestData").runStaticFunction("test", "receiver", "p0") 46 | } 47 | """ 48 | ) 49 | 50 | @Test 51 | fun `static default in companion`() = testJvmCompile( 52 | """ 53 | class TestData { 54 | companion object { 55 | @JvmStatic 56 | @JvmBlockingBridge 57 | suspend fun String.test(d: Int = 1): String{ 58 | assertEquals("receiver", this) 59 | assertEquals(1, d) 60 | return "OK" 61 | } 62 | } 63 | } 64 | """, noMain = true 65 | ) { 66 | classLoader.loadClass("TestData").kotlin.companionObjectInstance?.run { 67 | this::class.java.run { 68 | assertHasFunction("test", String::class, Int::class) // origin 69 | } 70 | runFunction("test", "receiver", 1) 71 | } 72 | classLoader.loadClass("TestData")?.run { 73 | assertHasFunction("test", String::class, Int::class) // origin 74 | 75 | runStaticFunction("test", "receiver", 1) 76 | } 77 | } 78 | 79 | @Test 80 | fun `static default with @JvmOverloads in companion`() = testJvmCompile( 81 | """ 82 | class TestData { 83 | companion object { 84 | @JvmStatic 85 | @JvmBlockingBridge 86 | @JvmOverloads 87 | suspend fun String.test(d: Int = 1): String{ 88 | assertEquals("receiver", this) 89 | assertEquals(1, d) 90 | return "OK" 91 | } 92 | } 93 | } 94 | """, noMain = true 95 | ) { 96 | classLoader.loadClass("TestData").kotlin.run { 97 | fun Class<*>.checkBoth() { 98 | assertHasFunction("test", String::class, Int::class) // origin 99 | assertHasFunction("test", String::class) // by @JvmDefault 100 | } 101 | 102 | java.run { 103 | checkBoth() 104 | 105 | runStaticFunction("test", "receiver", 1) 106 | runStaticFunction("test", "receiver") 107 | } 108 | companionObject!!.java.checkBoth() 109 | companionObjectInstance!!.run { 110 | runFunction("test", "receiver", 1) 111 | runFunction("test", "receiver") 112 | } 113 | } 114 | } 115 | 116 | @Test 117 | fun `member function in class companion`() = testJvmCompile( 118 | """ 119 | class TestData { 120 | companion object { 121 | @JvmStatic 122 | @JvmBlockingBridge 123 | suspend fun String.test(arg: String): String{ 124 | assertEquals("receiver", this) 125 | assertEquals("p0", arg) 126 | return "OK" 127 | } 128 | 129 | fun main(): String = this.runFunction("test", "receiver", "p0") 130 | } 131 | } 132 | """ 133 | ) 134 | 135 | @Test 136 | fun `static function in interface`() = testJvmCompile( 137 | """ 138 | interface TestData { 139 | companion object { 140 | @JvmStatic 141 | @JvmBlockingBridge 142 | suspend fun String.test(arg: String): String{ 143 | assertEquals("receiver", this) 144 | assertEquals("p0", arg) 145 | return "OK" 146 | } 147 | 148 | fun main(): String = Class.forName("TestData").runStaticFunction("test", "receiver", "p0") 149 | } 150 | } 151 | """ 152 | ) 153 | 154 | @Test 155 | fun `member function in interface companion`() = testJvmCompile( 156 | """ 157 | interface TestData { 158 | companion object { 159 | @JvmStatic 160 | @JvmBlockingBridge 161 | suspend fun String.test(arg: String): String{ 162 | assertEquals("receiver", this) 163 | assertEquals("p0", arg) 164 | return "OK" 165 | } 166 | 167 | fun main(): String = this.runFunction("test", "receiver", "p0") 168 | } 169 | } 170 | """ 171 | ) 172 | 173 | @Test 174 | fun `GeneratedJvmBlockingBridge annotation on static bridge`() = testJvmCompile( 175 | """ 176 | class TestData { 177 | companion object { 178 | @JvmStatic 179 | @JvmBlockingBridge 180 | suspend fun test(arg: String) {} 181 | } 182 | 183 | fun main(): String { 184 | Class.forName("TestData").getMethod("test", String::class.java). 185 | getAnnotation(Class.forName("me.him188.kotlin.jvm.blocking.bridge.GeneratedBlockingBridge") as Class) 186 | return "OK" 187 | } 188 | } 189 | """ 190 | ) 191 | 192 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/VisibilityAndModalityTest.kt: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import org.jetbrains.kotlin.descriptors.Modality.FINAL 4 | import org.jetbrains.kotlin.descriptors.Modality.OPEN 5 | import org.jetbrains.kotlin.descriptors.Visibilities 6 | import org.junit.jupiter.api.Test 7 | import java.lang.reflect.Modifier 8 | import kotlin.test.assertEquals 9 | 10 | internal class VisibilityAndModalityTest : AbstractCompilerTest() { 11 | @Test 12 | fun `final bridge for final function in final object`() = testJvmCompile( 13 | """ 14 | object TestData { 15 | @JvmBlockingBridge 16 | suspend fun test() {} 17 | } 18 | """, noMain = true 19 | ) { 20 | classLoader.loadClass("TestData").getMethod("test").run { 21 | assertEquals(Visibilities.Public, this.visibility) 22 | assertEquals(FINAL, this.modality) 23 | assertEquals(Modifier.FINAL or Modifier.PUBLIC, this.modifiers) 24 | } 25 | } 26 | 27 | @Test 28 | fun `open bridge for interfaces`() = testJvmCompile( 29 | """ 30 | interface TestData { 31 | @JvmBlockingBridge 32 | suspend fun test() {} 33 | 34 | @JvmBlockingBridge 35 | suspend fun test2() 36 | } 37 | """, noMain = true 38 | ) { 39 | classLoader.loadClass("TestData").getMethod("test").run { 40 | assertEquals(Visibilities.Public, this.visibility) 41 | assertEquals(OPEN, this.modality) 42 | assertEquals(Modifier.PUBLIC, this.modifiers) 43 | } 44 | classLoader.loadClass("TestData").getMethod("test2").run { 45 | assertEquals(Visibilities.Public, this.visibility) 46 | assertEquals(OPEN, this.modality) 47 | assertEquals(Modifier.PUBLIC, this.modifiers) 48 | } 49 | } 50 | 51 | @Test 52 | fun `open bridge for abstract classes`() = testJvmCompile( 53 | """ 54 | abstract class TestData { 55 | @JvmBlockingBridge 56 | open suspend fun test() {} 57 | 58 | @JvmBlockingBridge 59 | abstract suspend fun test2() 60 | } 61 | """, noMain = true 62 | ) { 63 | classLoader.loadClass("TestData").getMethod("test").run { 64 | assertEquals(Visibilities.Public, this.visibility) 65 | assertEquals(OPEN, this.modality) 66 | assertEquals(Modifier.PUBLIC, this.modifiers) 67 | } 68 | classLoader.loadClass("TestData").getMethod("test2").run { 69 | assertEquals(Visibilities.Public, this.visibility) 70 | assertEquals(OPEN, this.modality) 71 | assertEquals(Modifier.PUBLIC, this.modifiers) 72 | } 73 | } 74 | 75 | @Test 76 | fun `open bridge for sealed classes`() = testJvmCompile( 77 | """ 78 | sealed class TestData { 79 | @JvmBlockingBridge 80 | open suspend fun test() {} 81 | 82 | @JvmBlockingBridge 83 | abstract suspend fun test2() 84 | } 85 | """, noMain = true 86 | ) { 87 | classLoader.loadClass("TestData").getMethod("test").run { 88 | assertEquals(Visibilities.Public, this.visibility) 89 | assertEquals(OPEN, this.modality) 90 | assertEquals(Modifier.PUBLIC, this.modifiers) 91 | } 92 | classLoader.loadClass("TestData").getMethod("test2").run { 93 | assertEquals(Visibilities.Public, this.visibility) 94 | assertEquals(OPEN, this.modality) 95 | assertEquals(Modifier.PUBLIC, this.modifiers) 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/WithJavaTest.kt: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import org.junit.jupiter.api.Test 4 | 5 | internal class WithJavaTest : AbstractCompilerTest() { 6 | @Test 7 | fun `member function in class with Java`() = testJvmCompile( 8 | """ 9 | //package test 10 | object TestData { 11 | @JvmBlockingBridge 12 | suspend fun test() = "OK" 13 | 14 | fun main() = TestDataJ.main() 15 | } 16 | """, 17 | """ 18 | //package test; 19 | //import TestDataKt; 20 | public class TestDataJ { 21 | 22 | public static String main() { 23 | return TestData.INSTANCE.test(); 24 | } 25 | } 26 | """ 27 | ) 28 | 29 | @Test 30 | fun `static function in class from companion with Java`() = testJvmCompile( 31 | """ 32 | //package test 33 | class TestData { 34 | companion object { 35 | @JvmBlockingBridge 36 | @JvmStatic 37 | suspend fun test() = "OK" 38 | } 39 | 40 | fun main() = TestDataJ.main() 41 | } 42 | """, 43 | """ 44 | //package test; 45 | //import TestDataKt; 46 | public class TestDataJ { 47 | 48 | public static String main() { 49 | return TestData.test(); // static 50 | } 51 | } 52 | """ 53 | ) 54 | 55 | @Test 56 | fun `member function in class companion with Java`() = testJvmCompile( 57 | """ 58 | //package test 59 | class TestData { 60 | companion object { 61 | @JvmBlockingBridge 62 | @JvmStatic 63 | suspend fun test() = "OK" 64 | } 65 | 66 | fun main() = TestDataJ.main() 67 | } 68 | """, 69 | """ 70 | //package test; 71 | //import TestDataKt; 72 | public class TestDataJ { 73 | 74 | public static String main() { 75 | return TestData.Companion.test(); // member 76 | } 77 | } 78 | """ 79 | ) 80 | 81 | @Test 82 | fun `static function in interface from companion with Java`() = testJvmCompile( 83 | """ 84 | //package test 85 | interface TestData { 86 | companion object { 87 | @JvmBlockingBridge 88 | @JvmStatic 89 | suspend fun test() = "OK" 90 | 91 | fun main() = TestDataJ.main() 92 | } 93 | } 94 | """, 95 | """ 96 | //package test; 97 | //import TestDataKt; 98 | public class TestDataJ { 99 | 100 | public static String main() { 101 | return TestData.test(); // static 102 | } 103 | } 104 | """ 105 | ) 106 | 107 | @Test 108 | fun `member function in interface companion with Java`() = testJvmCompile( 109 | """ 110 | //package test 111 | interface TestData { 112 | companion object { 113 | @JvmBlockingBridge 114 | @JvmStatic 115 | suspend fun test() = "OK" 116 | 117 | fun main() = TestDataJ.main() 118 | } 119 | } 120 | """, 121 | """ 122 | //package test; 123 | //import TestDataKt; 124 | public class TestDataJ { 125 | 126 | public static String main() { 127 | return TestData.Companion.test(); // member 128 | } 129 | } 130 | """ 131 | ) 132 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/unit/AbstractUnitCoercionTest.kt: -------------------------------------------------------------------------------- 1 | package compiler.unit 2 | 3 | import compiler.AbstractCompilerTest 4 | import org.junit.jupiter.api.Test 5 | 6 | internal abstract class AbstractUnitCoercionTest : AbstractCompilerTest() { 7 | @Test 8 | fun member() = testJvmCompile( 9 | """ 10 | class TestData { 11 | @JvmBlockingBridge 12 | suspend fun String.test(arg: String) { // returns Unit 13 | assertEquals("receiver", this) 14 | assertEquals("p0", arg) 15 | } 16 | 17 | companion object { 18 | @JvmStatic 19 | fun main(): String { 20 | Class.forName("TestData").assertHasFunction("test", String::class.java, String::class.java) 21 | return "OK" 22 | } 23 | } 24 | } 25 | """ 26 | ) 27 | 28 | @Test 29 | fun `member non unit return type`() = testJvmCompile( 30 | """ 31 | class TestData { 32 | @JvmBlockingBridge 33 | suspend fun test(arg: String): String { // returns Unit 34 | return "" 35 | } 36 | 37 | companion object { 38 | @JvmStatic 39 | fun main(): String { 40 | Class.forName("TestData").assertHasFunction("test", String::class.java) 41 | return "OK" 42 | } 43 | } 44 | } 45 | """ 46 | ) 47 | 48 | @Test 49 | fun static() = testJvmCompile( 50 | """ 51 | object TestData { 52 | @JvmStatic 53 | @JvmBlockingBridge 54 | suspend fun String.test(arg: String) { // returns Unit 55 | } 56 | 57 | fun main(): String { 58 | Class.forName("TestData").assertHasFunction("test", String::class.java, String::class.java) 59 | return "OK" 60 | } 61 | } 62 | """ 63 | ) 64 | 65 | @Test 66 | fun `static companion`() = testJvmCompile( 67 | """ 68 | class TestData { 69 | companion object { 70 | @JvmStatic 71 | @JvmBlockingBridge 72 | suspend fun String.test(arg: String) { // returns Unit 73 | } 74 | 75 | @JvmStatic 76 | fun main(): String { 77 | Class.forName("TestData").assertHasFunction("test", String::class.java, String::class.java) 78 | Class.forName("TestData\${'$'}Companion").assertHasFunction("test", String::class.java, String::class.java) 79 | return "OK" 80 | } 81 | } 82 | } 83 | """ 84 | ) 85 | 86 | 87 | @Test 88 | fun `static companion non unit`() = testJvmCompile( 89 | """ 90 | class TestData { 91 | companion object { 92 | @JvmStatic 93 | @JvmBlockingBridge 94 | suspend fun String.test(arg: String): String { // returns Unit 95 | assertEquals("receiver", this) 96 | assertEquals("p0", arg) 97 | return "OK" 98 | } 99 | 100 | @JvmStatic 101 | fun main(): String { 102 | Class.forName("TestData").assertHasFunction("test", String::class.java, String::class.java) 103 | Class.forName("TestData\${'$'}Companion").assertHasFunction("test", String::class.java, String::class.java) 104 | 105 | return Class.forName("TestData").runStaticFunction("test", "receiver", "p0") 106 | } 107 | } 108 | } 109 | """ 110 | ) 111 | } -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/compiler/unit/UnitCoercionTest.kt: -------------------------------------------------------------------------------- 1 | package compiler.unit 2 | 3 | internal class UnitCoercionTest : AbstractUnitCoercionTest() 4 | 5 | -------------------------------------------------------------------------------- /compiler-plugin/src/test/kotlin/util.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST", "unused") 2 | 3 | import com.tschuchort.compiletesting.KotlinCompilation 4 | import com.tschuchort.compiletesting.SourceFile 5 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 6 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.BridgeComponentRegistrar 7 | import org.intellij.lang.annotations.Language 8 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 9 | import org.jetbrains.kotlin.config.CompilerConfiguration 10 | import org.jetbrains.kotlin.config.JvmTarget 11 | import org.jetbrains.kotlin.descriptors.Modality 12 | import org.jetbrains.kotlin.descriptors.Visibilities 13 | import org.jetbrains.kotlin.descriptors.Visibility 14 | import java.io.File 15 | import java.lang.reflect.Method 16 | import java.lang.reflect.Modifier 17 | import java.util.* 18 | import kotlin.reflect.KClass 19 | import kotlin.reflect.KParameter 20 | import kotlin.reflect.full.companionObjectInstance 21 | import kotlin.test.assertEquals 22 | 23 | // Expose to top-package for TestData 24 | typealias JvmBlockingBridge = JvmBlockingBridge 25 | 26 | fun Class.createInstance(): T { 27 | return kotlin.objectInstance ?: kotlin.createInstanceOrNull() ?: getConstructor().newInstance() 28 | } 29 | 30 | @Deprecated( 31 | "runFunction on class is an error.", 32 | replaceWith = ReplaceWith("createInstance().run { \n runFunction(name)\n }"), 33 | level = DeprecationLevel.ERROR 34 | ) 35 | fun Class<*>.runFunction(name: String): Nothing { 36 | error("runFunction on class is an error.") 37 | } 38 | 39 | @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") 40 | @kotlin.internal.LowPriorityInOverloadResolution 41 | @Deprecated( 42 | "runFunction on class is an error.", 43 | replaceWith = ReplaceWith("createInstance().run { \n runFunction(name, args)\n }"), 44 | level = DeprecationLevel.ERROR 45 | ) 46 | inline fun Class<*>.runFunction(name: String, vararg args: Any): Nothing { 47 | error("runFunction on class is an error.") 48 | } 49 | 50 | fun Any.runFunction(name: String, vararg args: Any): R { 51 | return this::class.java.getMethod(name, *args.map { it::class.javaPrimitiveType ?: it::class.java }.toTypedArray()) 52 | .invoke(this, *args) as R 53 | } 54 | 55 | fun Class<*>.runStaticFunction(name: String, vararg args: Any): R { 56 | return getMethod(name, *args.map { it::class.javaPrimitiveType ?: it::class.java }.toTypedArray()).also { 57 | assert(Modifier.isStatic(it.modifiers)) { "method $name is not static" } 58 | }.invoke(null, *args)!! as R 59 | } 60 | 61 | fun Class<*>.getFunctionReturnType(name: String, vararg args: Class<*>): String { 62 | return getMethod(name, *args).returnType.canonicalName 63 | } 64 | 65 | inline fun Class<*>.assertHasFunction( 66 | name: String, 67 | vararg args: Class<*>, 68 | declaredOnly: Boolean = false, 69 | noinline runIfFound: Method.() -> Unit = {}, 70 | ) { 71 | return assertHasFunction( 72 | name, 73 | args = args, 74 | returnType = R::class.javaPrimitiveType ?: R::class.java, 75 | declaredOnly, 76 | runIfFound = runIfFound 77 | ) 78 | } 79 | 80 | @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") 81 | @kotlin.internal.LowPriorityInOverloadResolution 82 | inline fun Class<*>.assertHasFunction( 83 | name: String, 84 | vararg args: KClass<*>, 85 | declaredOnly: Boolean = false, 86 | noinline runIfFound: Method.() -> Unit = {}, 87 | ) { 88 | return assertHasFunction( 89 | name, 90 | args = args.map { it.javaPrimitiveType ?: it.java }.toTypedArray(), 91 | returnType = R::class.javaPrimitiveType ?: R::class.java, 92 | declaredOnly, 93 | runIfFound = runIfFound 94 | ) 95 | } 96 | 97 | inline fun Class<*>.assertNoFunction( 98 | name: String, 99 | vararg args: Class<*>, 100 | declaredOnly: Boolean = false, 101 | ) { 102 | return assertNoFunction( 103 | name, 104 | args = args, 105 | returnType = R::class.javaPrimitiveType ?: R::class.java, 106 | declaredOnly = declaredOnly 107 | ) 108 | } 109 | 110 | inline fun Class<*>.getFunctionWithReturnType(name: String, vararg args: Class<*>): Method { 111 | val returnType = R::class.javaPrimitiveType ?: R::class.java 112 | val ret = allMethods.find { 113 | it.name == name && 114 | it.returnType == returnType && 115 | it.parameterCount == args.size && 116 | it.parameters.zip(args).all { (param, clazz) -> param.type == clazz } 117 | } 118 | 119 | return ret 120 | ?: throw AssertionError( 121 | "Class '${this.name}' does not have method $name(${args.joinToString { it.canonicalName }})${returnType.canonicalName}. All methods list: " + 122 | "\n${allMethods.joinToString("\n")}\n" 123 | ) 124 | } 125 | 126 | val Class<*>.allMethods: Set 127 | get() { 128 | fun Class<*>?.shouldInclude(): Boolean { 129 | if (this == null) return false 130 | return !this.`package`.name.startsWith("java") 131 | && !this.`package`.name.startsWith("kotlin") 132 | } 133 | 134 | val set = declaredMethods.toMutableSet() 135 | set += superclass.takeIf { it.shouldInclude() }?.allMethods.orEmpty() 136 | set += interfaces.flatMap { 137 | it.takeIf { it.shouldInclude() }?.allMethods.orEmpty() 138 | } 139 | 140 | return set 141 | } 142 | 143 | fun Class<*>.assertHasFunction( 144 | name: String, 145 | vararg args: Class<*>, 146 | returnType: Class<*>, 147 | declaredOnly: Boolean = false, 148 | runIfFound: Method.() -> Unit, 149 | ) { 150 | val any = (if (declaredOnly) declaredMethods.toSet() else allMethods).find { 151 | it.name == name && 152 | it.returnType == returnType && 153 | it.parameterCount == args.size && 154 | it.parameters.zip(args).all { (param, clazz) -> param.type == clazz } 155 | } 156 | ?: throw AssertionError( 157 | "Class '${this.name}' does not have method $name(${args.joinToString { it.canonicalName }})${returnType.canonicalName}. All methods list: " + 158 | "\n${allMethods.joinToString("\n")}\n" 159 | ) 160 | 161 | runIfFound(any) 162 | } 163 | 164 | fun Class<*>.assertNoFunction( 165 | name: String, 166 | vararg args: Class<*>, 167 | returnType: Class<*>, 168 | declaredOnly: Boolean = false, 169 | ) { 170 | val any = (if (declaredOnly) declaredMethods.toSet() else allMethods).any { 171 | it.name == name && 172 | it.returnType == returnType && 173 | it.parameterCount == args.size && 174 | it.parameters.zip(args).all { (param, clazz) -> param.type == clazz } 175 | } 176 | if (any) 177 | throw AssertionError("Class '${this.name}' does has method $name(${args.joinToString { it.canonicalName }})${returnType.canonicalName}") 178 | } 179 | 180 | 181 | @SinceKotlin("1.1") 182 | fun KClass.createInstanceOrNull(): T? { 183 | // TODO: throw a meaningful exception 184 | val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) } 185 | ?: return null 186 | 187 | return noArgsConstructor.callBy(emptyMap()) 188 | } 189 | -------------------------------------------------------------------------------- /gradle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/VersionGenerated.kt -------------------------------------------------------------------------------- /gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | kotlin("jvm") 4 | kotlin("kapt") 5 | id("java-gradle-plugin") 6 | `maven-publish` 7 | id("com.gradle.plugin-publish") 8 | } 9 | 10 | kotlin.targets.asSequence() 11 | .flatMap { it.compilations } 12 | .filter { it.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm } 13 | .map { it.kotlinOptions } 14 | .filterIsInstance() 15 | .forEach { it.jvmTarget = "1.8" } 16 | 17 | java { 18 | sourceCompatibility = JavaVersion.VERSION_1_8 19 | targetCompatibility = JavaVersion.VERSION_1_8 20 | } 21 | 22 | dependencies { 23 | compileOnly(kotlin("stdlib")) 24 | compileOnly(gradleApi()) 25 | compileOnly(kotlin("gradle-plugin-api")) 26 | compileOnly(kotlin("gradle-plugin")) 27 | compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:${Versions.kotlin}") 28 | 29 | api(project(":kotlin-jvm-blocking-bridge-compiler")) 30 | } 31 | 32 | pluginBundle { 33 | website = "https://github.com/him188/kotlin-jvm-blocking-bridge" 34 | vcsUrl = "https://github.com/him188/kotlin-jvm-blocking-bridge.git" 35 | tags = listOf("kotlin", "jvm-blocking-bridge") 36 | } 37 | 38 | gradlePlugin { 39 | plugins { 40 | create("kotlinJvmBlockingBridge") { 41 | id = "me.him188.kotlin-jvm-blocking-bridge" 42 | displayName = "Kotlin JVM Blocking Bridge" 43 | description = project.description 44 | implementationClass = "me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridgeGradlePlugin" 45 | } 46 | } 47 | } 48 | 49 | tasks.register("updateKJBBVersion") { 50 | doLast { 51 | project.projectDir.resolve("src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge") 52 | .resolve("VersionGenerated.kt") 53 | .apply { createNewFile() } 54 | .writeText( 55 | """ 56 | package me.him188.kotlin.jvm.blocking.bridge 57 | 58 | internal const val KJBB_VERSION = "${Versions.project}" 59 | """.trimIndent() 60 | ) 61 | } 62 | } 63 | 64 | tasks.getByName("compileKotlin").dependsOn("updateKJBBVersion") 65 | 66 | /* 67 | tasks.getByName("shadowJar", com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar::class) { 68 | archiveClassifier.set("") 69 | } 70 | 71 | tasks.publishPlugins.get().dependsOn(tasks.shadowJar.get())*/ 72 | 73 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/BlockingBridgePluginExtension.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 4 | 5 | open class BlockingBridgePluginExtension : IBridgeConfiguration { 6 | /** 7 | * Generate blocking bridges for all effectively public suspend functions in the module where possible, 8 | * even if they are not annotated with @JvmBlockingBridge. 9 | * 10 | * @since 1.10 11 | */ 12 | override var enableForModule: Boolean = false 13 | 14 | // var enabled: Boolean = true 15 | } -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RedundantVisibilityModifier", "unused") 2 | 3 | package me.him188.kotlin.jvm.blocking.bridge 4 | 5 | import org.gradle.api.artifacts.dsl.DependencyHandler 6 | 7 | public fun DependencyHandler.jvmBlockingBridge( 8 | version: String? = KJBB_VERSION, 9 | ): Any { 10 | return "me.him188:kotlin-jvm-blocking-bridge:$version" 11 | } -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/JvmBlockingBridgeGradlePlugin.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.JvmBlockingBridgeCompilerConfigurationKeys.ENABLE_FOR_MODULE 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.BridgeCommandLineProcessor 5 | import org.gradle.api.Project 6 | import org.gradle.api.provider.Provider 7 | import org.jetbrains.kotlin.gradle.plugin.* 8 | 9 | 10 | internal fun BlockingBridgePluginExtension.toSubpluginOptionList(): List { 11 | return listOf( 12 | SubpluginOption(ENABLE_FOR_MODULE.toString(), enableForModule.toString()), 13 | ) 14 | } 15 | 16 | /** 17 | * Would download from maven central 18 | */ 19 | private val pluginArtifact = SubpluginArtifact( 20 | groupId = "me.him188", 21 | artifactId = "kotlin-jvm-blocking-bridge-compiler-embeddable", 22 | version = KJBB_VERSION 23 | ) // .also { log("Adding: " + it.groupId + ":${it.artifactId}:${it.version}") } 24 | 25 | 26 | open class JvmBlockingBridgeGradlePlugin : KotlinCompilerPluginSupportPlugin { 27 | override fun apply(target: Project) { 28 | target.extensions.create("blockingBridge", BlockingBridgePluginExtension::class.java) 29 | } 30 | 31 | override fun getCompilerPluginId(): String = BridgeCommandLineProcessor.COMPILER_PLUGIN_ID 32 | 33 | override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> { 34 | val project = kotlinCompilation.target.project 35 | val ext: BlockingBridgePluginExtension? = 36 | project.extensions.findByType(BlockingBridgePluginExtension::class.java) 37 | return project.provider { 38 | mutableListOf() 39 | ext?.toSubpluginOptionList() ?: emptyList() 40 | } 41 | } 42 | 43 | override fun getPluginArtifact(): SubpluginArtifact = pluginArtifact 44 | 45 | override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { 46 | 47 | return kotlinCompilation.target.project.plugins.hasPlugin(JvmBlockingBridgeGradlePlugin::class.java) 48 | && when (kotlinCompilation.platformType) { 49 | KotlinPlatformType.jvm, 50 | KotlinPlatformType.androidJvm, 51 | KotlinPlatformType.common, 52 | -> true 53 | 54 | else -> false 55 | }//.also { log("Application to ${kotlinCompilation.name} (${kotlinCompilation.platformType}): $it") } 56 | } 57 | } 58 | 59 | private fun log(msg: String) = println("***JvmBlockingBridge: $msg") -------------------------------------------------------------------------------- /gradle-plugin/src/main/resources/META-INF/services_/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin: -------------------------------------------------------------------------------- 1 | me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridgeGradleSubPlugin -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # style guide 2 | kotlin.code.style=official 3 | #org.gradle.parallel=true 4 | #kotlin.parallel.tasks.in.project=true 5 | kotlin.mpp.stability.nowarn=true 6 | systemProp.org.gradle.internal.publish.checksums.insecure=true 7 | org.gradle.vfs.watch=true 8 | kotlin.native.ignoreIncorrectDependencies=true 9 | kotlin.js.compiler=both 10 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Him188/kotlin-jvm-blocking-bridge/1d30ac7d8420783e6275f116cbb264df4932aa5e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 04 22:27:09 CST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /ide-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 2 | 3 | plugins { 4 | id("org.jetbrains.intellij") version "1.15.0" 5 | kotlin("jvm") 6 | kotlin("plugin.serialization") 7 | 8 | // id("com.github.johnrengelman.shadow") 9 | } 10 | 11 | kotlin.targets.asSequence() 12 | .flatMap { it.compilations } 13 | .filter { it.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm } 14 | .map { it.kotlinOptions } 15 | .filterIsInstance() 16 | .forEach { it.jvmTarget = "17" } 17 | 18 | java { 19 | sourceCompatibility = JavaVersion.VERSION_17 20 | targetCompatibility = JavaVersion.VERSION_17 21 | } 22 | 23 | dependencies { 24 | api(project(":kotlin-jvm-blocking-bridge-runtime")) 25 | api(project(":kotlin-jvm-blocking-bridge-compiler")) 26 | 27 | // compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlin}") 28 | compileOnly(fileTree("run/idea-sandbox/plugins/Kotlin/lib").filter { 29 | !it.name.contains("stdlib") && !it.name.contains("coroutines") 30 | }) 31 | } 32 | 33 | group = "net.mamoe" 34 | version = Versions.idePlugin 35 | 36 | // See https://github.com/JetBrains/gradle-intellij-plugin/ 37 | intellij { 38 | version.set(Versions.intellij) 39 | downloadSources.set(true) 40 | updateSinceUntilBuild.set(false) 41 | 42 | sandboxDir.set(projectDir.resolve("run/idea-sandbox").absolutePath) 43 | 44 | plugins.set( 45 | listOf( 46 | // "org.jetbrains.kotlin:211-1.5.30-M1-release-141-IJ7442.40@eap", 47 | "java", 48 | if (Versions.kotlinIdea == null) 49 | "org.jetbrains.kotlin" 50 | else 51 | "org.jetbrains.kotlin:${Versions.kotlinIdea}" 52 | ) 53 | ) 54 | } 55 | 56 | tasks.getByName("publishPlugin", org.jetbrains.intellij.tasks.PublishPluginTask::class) { 57 | val pluginKey = project.findProperty("jetbrains.hub.key")?.toString() 58 | if (pluginKey != null) { 59 | logger.info("Found jetbrains.hub.key") 60 | token.set(pluginKey) 61 | } else { 62 | logger.info("jetbrains.hub.key not found") 63 | } 64 | } 65 | 66 | tasks.withType { 67 | sinceBuild.set("231.0") 68 | untilBuild.set("232.*") 69 | changeNotes.set( 70 | """ 71 | See Release notes 72 | """.trimIndent() 73 | ) 74 | } 75 | 76 | tasks.withType(KotlinJvmCompile::class) { 77 | kotlinOptions.freeCompilerArgs += "-Xjvm-default=all" 78 | } 79 | 80 | //val theProject = project 81 | 82 | //tasks.getByName("shadowJar", com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar::class) { 83 | // archiveClassifier.set("") 84 | // this.dependencyFilter.exclude { 85 | // it.name.contains("intellij", ignoreCase = true) || it.name.contains("idea", ignoreCase = true) 86 | // } 87 | // exclude { 88 | // // exclude ComponentRegistrar which is for CLI compiler. 89 | // it.name == "org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar" && !it.path.contains(theProject.path) 90 | // } 91 | //} 92 | 93 | //tasks.buildPlugin.get().dependsOn(tasks.shadowJar.get()) 94 | -------------------------------------------------------------------------------- /ide-plugin/libs/ide-common.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Him188/kotlin-jvm-blocking-bridge/1d30ac7d8420783e6275f116cbb264df4932aa5e/ide-plugin/libs/ide-common.jar -------------------------------------------------------------------------------- /ide-plugin/run/below8/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | id("me.him188.kotlin-jvm-blocking-bridge") 4 | } 5 | 6 | blockingBridge { 7 | } -------------------------------------------------------------------------------- /ide-plugin/run/below8/src/main/kotlin/Below8.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | @JvmBlockingBridge // should error 4 | interface Below8 5 | -------------------------------------------------------------------------------- /ide-plugin/run/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") version "1.8.20" 5 | id("me.him188.kotlin-jvm-blocking-bridge") version "3.0.0-180.1" 6 | } 7 | 8 | blockingBridge { 9 | // enableForModule = true 10 | } 11 | 12 | group = "me.him188" 13 | version = "1.0-SNAPSHOT" 14 | 15 | allprojects { 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | } 21 | 22 | dependencies { 23 | api("me.him188:kotlin-jvm-blocking-bridge-runtime:3.0.0-180.1") 24 | } 25 | 26 | tasks.withType() { 27 | kotlinOptions.jvmTarget = "1.8" 28 | kotlinOptions.freeCompilerArgs += "-Xjvm-default=all" 29 | } -------------------------------------------------------------------------------- /ide-plugin/run/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.mpp.enableGranularSourceSetsMetadata=true 3 | kotlin.native.enableDependencyPropagation=false 4 | kotlin.js.generate.executable.default=false 5 | -------------------------------------------------------------------------------- /ide-plugin/run/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Him188/kotlin-jvm-blocking-bridge/1d30ac7d8420783e6275f116cbb264df4932aa5e/ide-plugin/run/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ide-plugin/run/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /ide-plugin/run/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /ide-plugin/run/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ide-plugin/run/mpp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | } 4 | 5 | group = "me.him188" 6 | version = "1.0-SNAPSHOT" 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | kotlin { 13 | jvm { 14 | compilations.all { 15 | kotlinOptions.jvmTarget = "1.8" 16 | } 17 | withJava() 18 | testRuns["test"].executionTask.configure { 19 | useJUnitPlatform() 20 | } 21 | } 22 | js(BOTH) { 23 | browser { 24 | commonWebpackConfig { 25 | } 26 | } 27 | } 28 | val hostOs = System.getProperty("os.name") 29 | val isMingwX64 = hostOs.startsWith("Windows") 30 | val nativeTarget = when { 31 | hostOs == "Mac OS X" -> macosX64("native") 32 | hostOs == "Linux" -> linuxX64("native") 33 | isMingwX64 -> mingwX64("native") 34 | else -> throw GradleException("Host OS is not supported in Kotlin/Native.") 35 | } 36 | 37 | 38 | sourceSets { 39 | val commonMain by getting 40 | val commonTest by getting { 41 | dependencies { 42 | implementation(kotlin("test")) 43 | } 44 | } 45 | val jvmMain by getting 46 | val jvmTest by getting 47 | val jsMain by getting 48 | val jsTest by getting 49 | val nativeMain by getting 50 | val nativeTest by getting 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ide-plugin/run/mpp/src/commonMain/kotlin/common.kt: -------------------------------------------------------------------------------- 1 | 2 | @JvmBlockingBridge 3 | interface AnnotationOnInterface { 4 | 5 | // 6 | fun nonSuspend() { 7 | 8 | } 9 | 10 | @JvmSynthetic 11 | suspend fun suspend() { 12 | } 13 | } -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") version "1.8.0" 3 | id("me.him188.kotlin-jvm-blocking-bridge") version "2.2.0-180.1" 4 | } 5 | 6 | group = "me.him188" 7 | version = "1.0-SNAPSHOT" 8 | 9 | repositories { 10 | mavenCentral() 11 | mavenLocal() 12 | } 13 | 14 | kotlin { 15 | jvm { 16 | compilations.all { 17 | kotlinOptions.jvmTarget = "1.8" 18 | } 19 | withJava() 20 | testRuns["test"].executionTask.configure { 21 | useJUnitPlatform() 22 | } 23 | } 24 | val hostOs = System.getProperty("os.name") 25 | val isMingwX64 = hostOs.startsWith("Windows") 26 | val nativeTarget = when { 27 | hostOs == "Mac OS X" -> macosX64("native") 28 | hostOs == "Linux" -> linuxX64("native") 29 | isMingwX64 -> mingwX64("native") 30 | else -> throw GradleException("Host OS is not supported in Kotlin/Native.") 31 | } 32 | 33 | 34 | js(IR) { 35 | binaries.executable() 36 | nodejs { 37 | 38 | } 39 | } 40 | sourceSets { 41 | val commonMain by getting { 42 | dependencies { 43 | 44 | } 45 | } 46 | val commonTest by getting { 47 | dependencies { 48 | implementation(kotlin("test")) 49 | } 50 | } 51 | val jvmMain by getting 52 | val jvmTest by getting 53 | val nativeMain by getting 54 | val nativeTest by getting 55 | val jsMain by getting 56 | val jsTest by getting 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.js.compiler=ir 3 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Him188/kotlin-jvm-blocking-bridge/1d30ac7d8420783e6275f116cbb264df4932aa5e/ide-plugin/run/mppCommon/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "untitled" 3 | 4 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/src/commonMain/kotlin/Test.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | import kotlin.coroutines.suspendCoroutine 3 | import kotlin.jvm.JvmSynthetic 4 | 5 | @JvmBlockingBridge 6 | interface A { 7 | 8 | suspend fun member() { 9 | suspendCoroutine {} 10 | 11 | } 12 | 13 | class A { 14 | 15 | suspend fun member() { 16 | suspendCoroutine {} 17 | 18 | } 19 | } 20 | } 21 | 22 | interface Override : A { 23 | override suspend fun member() { 24 | suspendCoroutine {} 25 | } 26 | } -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/src/commonMain/kotlin/Top.kt: -------------------------------------------------------------------------------- 1 | @file:JvmBlockingBridge 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 4 | import kotlin.coroutines.suspendCoroutine 5 | 6 | /** 7 | * OK 8 | */ 9 | suspend fun topLevel() { 10 | suspendCoroutine {} 11 | } 12 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/src/jsMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import kotlin.coroutines.suspendCoroutine 2 | 3 | fun main() { 4 | println(greeting("js")) 5 | } 6 | 7 | fun greeting(name: String) = 8 | "Hello, $name" 9 | 10 | suspend fun js() { 11 | suspendCoroutine { } 12 | } 13 | 14 | interface JsInterface { 15 | suspend fun jsInterfaceMember() { 16 | suspendCoroutine { } 17 | } 18 | } -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/src/jsTest/kotlin/GreetingTest.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.Test 2 | import kotlin.test.assertEquals 3 | 4 | class GreetingTest { 5 | @Test 6 | fun testGreeting() { 7 | assertEquals(greeting("World"), "Hello, World") 8 | } 9 | } -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/src/jvmMain/java/Java.java: -------------------------------------------------------------------------------- 1 | public class Java { 2 | 3 | public static void main(String[] args) { 4 | TopKt.topLevel(); 5 | KtKt.fn(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ide-plugin/run/mppCommon/src/jvmMain/kotlin/Kt.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | import kotlin.coroutines.suspendCoroutine 3 | 4 | @JvmBlockingBridge 5 | suspend fun fn() { 6 | suspendCoroutine { } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /ide-plugin/run/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | 8 | } 9 | rootProject.name = "TestProject" 10 | 11 | 12 | include(":below8") 13 | include(":mpp") -------------------------------------------------------------------------------- /ide-plugin/run/src/main/java/CallsFromJava.java: -------------------------------------------------------------------------------- 1 | import kotlin.Unit; 2 | import kotlin.coroutines.Continuation; 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.io.IOException; 7 | 8 | public class CallsFromJava { 9 | public static void main(String[] args) { 10 | testDeprecated(); 11 | testReturnValue(); 12 | testThrows(); 13 | testInheritance(); 14 | testSuspendStatic(); 15 | testJvmOverloads(); 16 | testSynthetic(); 17 | testVisibility(); 18 | } 19 | 20 | private static void testVisibility() { 21 | TestEffectivePublic.INSTANCE.effectivePublic(null); // original 22 | TestEffectivePublic.INSTANCE.effectivePublic(); // `@PublishedApi internal` in Kotlin, should be ok. 23 | TestEffectivePublic.INSTANCE.privateFun(); // private in Kotlin, forbid. 24 | } 25 | 26 | private static void testReturnValue() { 27 | Object any = new AClass().suspendMember(); // return value must be void (error here) 28 | String b = new TestRet().suspendMember(); // return value must be String 29 | } 30 | 31 | private static void testDeprecated() { 32 | String s = new TestRet().deprecatedSuspendMember(); // should report deprecation, return value must be String 33 | } 34 | 35 | private static void testSuspendStatic() { 36 | AClass.Companion.ordinaryStaticInCompanion(); 37 | 38 | AClass.ordinaryStaticInCompanion(); 39 | AClass.suspendStaticInCompanion(); // should report deprecation 40 | AClass.suspendStaticInCompanion(null); // original suspend function should still be callable 41 | } 42 | 43 | private static void testThrows() { 44 | try { 45 | AClass.P.suspendThrowsInObject(); 46 | } catch (IOException ignored) { 47 | } 48 | } 49 | 50 | private static void testInheritance() { 51 | new AClass() { 52 | // should be able to override original suspend function 53 | @Nullable 54 | @Override 55 | public Object suspendMember(@NotNull Continuation $completion) { 56 | return super.suspendMember($completion); 57 | } 58 | 59 | // should be able to override generated overload 60 | @Override 61 | public void overloads() { 62 | super.overloads(); 63 | } 64 | 65 | // should be able to override generated bridge 66 | @Override 67 | public void suspendMember() { 68 | super.suspendMember(); 69 | } 70 | }; 71 | } 72 | 73 | private static void testSynthetic() { 74 | AnnotationOnInterface i = new AnnotationOnInterface() { 75 | }; 76 | 77 | i.suspendMember(); // allow calls to suspend members 78 | i.synthetic(); // forbid calls to synthetic functions 79 | } 80 | 81 | private static void testJvmOverloads() { 82 | AClass x = new AClass(); 83 | 84 | x.overloads(1, "", null); // original suspend function 85 | x.overloads(1, (Continuation) null); // original suspend function, by JvmOverloads 86 | x.overloads(null); // original suspend function, by JvmOverloads 87 | 88 | x.overloads(1); // source-level non-suspend overload, should clash for now. 89 | x.overloads(1, ""); // should be ok 90 | x.overloads(1); // should be ok 91 | x.overloads(); // should be ok 92 | 93 | x.overloadsClash(1, "", null); // original suspend function 94 | x.overloadsClash(1); // source-level non-suspend overload, should clash for now. 95 | x.overloadsClash(1, ""); // Should be ok 96 | x.overloadsClash(1, ""); // Should be ok 97 | } 98 | } -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/AnnotationOnClass.kt: -------------------------------------------------------------------------------- 1 | @file:JvmBlockingBridge 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 4 | 5 | interface TestAnnotationsOnClass { 6 | 7 | 8 | /** 9 | * x 10 | */ 11 | @JvmSynthetic 12 | suspend fun test() { 13 | 14 | } 15 | 16 | object X { 17 | 18 | suspend fun test() { 19 | 20 | } 21 | 22 | @JvmOverloads 23 | suspend fun test(s: String, v: Int = 1) { 24 | 25 | } 26 | 27 | internal suspend fun test2() { 28 | 29 | } 30 | 31 | @PublishedApi 32 | internal suspend fun test3() { 33 | 34 | } 35 | 36 | @JvmBlockingBridge 37 | class Inner { 38 | 39 | @JvmSynthetic 40 | suspend fun test() { 41 | 42 | } 43 | } 44 | } 45 | } 46 | 47 | interface TestAnnotationsOnClass2 : TestAnnotationsOnClass 48 | interface TestAnnotationsOnClass21 : TestAnnotationsOnClass 49 | interface TestAnnotationsOnClass211 : TestAnnotationsOnClass 50 | interface TestAnnotationsOnClass2111 : TestAnnotationsOnClass 51 | interface TestAnnotationsOnClass21111 : TestAnnotationsOnClass -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/AnnotationOnFile.kt: -------------------------------------------------------------------------------- 1 | @file:JvmBlockingBridge 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 4 | 5 | suspend fun test() { 6 | 7 | } 8 | 9 | @JvmBlockingBridge 10 | suspend fun test2() { 11 | 12 | } -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/AnnotationOnInterface.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | @JvmBlockingBridge 4 | interface AnnotationOnInterface { 5 | 6 | // 7 | suspend fun suspendMember() { 8 | 9 | } 10 | 11 | @JvmSynthetic 12 | suspend fun synthetic() { 13 | } 14 | } -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/Overloads.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | @JvmBlockingBridge 4 | object X { 5 | @JvmStatic 6 | @JvmOverloads 7 | suspend fun test(s: String, v: Int = 1) { 8 | } 9 | } -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/SyntheticStaticInCompanion.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | @JvmBlockingBridge 4 | class SyntheticStaticInCompanion { 5 | 6 | @JvmBlockingBridge 7 | companion object { 8 | 9 | @JvmStatic 10 | suspend fun syn() { 11 | 12 | 13 | } 14 | } 15 | } 16 | 17 | object A { 18 | 19 | @JvmBlockingBridge 20 | @JvmStatic 21 | suspend fun syn() { 22 | } 23 | 24 | @JvmBlockingBridge 25 | suspend fun syn1() { 26 | } 27 | } 28 | 29 | class B { 30 | @JvmBlockingBridge 31 | suspend fun syn() { 32 | } 33 | } -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/TestEffectivePublic.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | @JvmBlockingBridge 4 | object TestEffectivePublic { 5 | 6 | // private so no auto @JvmBlockingBridge 7 | private suspend fun privateFun() {} 8 | 9 | // effectively public so auto @JvmBlockingBridge 10 | @PublishedApi 11 | internal suspend fun effectivePublic() { 12 | } 13 | 14 | @JvmStatic 15 | fun main(args: Array) { 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/TestRet.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | open class TestRet2 { 4 | @JvmBlockingBridge 5 | @JvmOverloads 6 | open suspend fun test(ii: Int = 1): String = "" 7 | } 8 | -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RedundantSuspendModifier") 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 4 | import java.io.IOException 5 | import kotlin.jvm.Throws 6 | 7 | fun main() { 8 | 9 | } 10 | 11 | open class AClass { 12 | 13 | @JvmBlockingBridge 14 | @JvmOverloads 15 | open suspend fun overloadsClash(x: Int = 1, s: String = "") { 16 | 17 | } 18 | 19 | fun overloadsClash(x: Int) { // should report an error but not implemented yet. 20 | 21 | } 22 | 23 | @JvmBlockingBridge 24 | @JvmOverloads 25 | open suspend fun overloads(x: Int = 1, s: String = "") { 26 | 27 | } 28 | 29 | 30 | /** 31 | * K fun 32 | */ 33 | @JvmBlockingBridge 34 | open suspend fun suspendMember() { 35 | } 36 | 37 | companion object { 38 | 39 | @JvmStatic 40 | @JvmBlockingBridge 41 | @Deprecated("") 42 | suspend fun suspendStaticInCompanion() { 43 | } 44 | 45 | @JvmStatic 46 | fun ordinaryStaticInCompanion() { 47 | } 48 | } 49 | 50 | object P { 51 | 52 | @JvmStatic 53 | @JvmBlockingBridge 54 | @Throws(IOException::class) 55 | suspend fun suspendThrowsInObject() { 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ide-plugin/run/src/main/kotlin/ret.kt: -------------------------------------------------------------------------------- 1 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge 2 | 3 | open class TestRet { 4 | @JvmBlockingBridge 5 | @MyDeprecated("asd") 6 | open suspend fun deprecatedSuspendMember(): String = "" 7 | 8 | @JvmBlockingBridge 9 | suspend fun suspendMember(): String = "" 10 | } 11 | 12 | typealias MyDeprecated = Deprecated -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/BridgeModuleCacheService.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 4 | 5 | class BridgeModuleCacheService { 6 | var compilerEnabled: Boolean = true 7 | var config: IBridgeConfiguration = IBridgeConfiguration.Default 8 | 9 | var initialized = false 10 | } -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/BridgeProjectImportListener.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId 5 | import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter 6 | import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType 7 | import com.intellij.openapi.progress.util.BackgroundTaskUtil 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.project.modules 10 | import com.intellij.openapi.util.Computable 11 | 12 | class BridgeProjectImportListener : Disposable, ExternalSystemTaskNotificationListenerAdapter() { 13 | override fun dispose() { 14 | } 15 | 16 | 17 | override fun onEnd(id: ExternalSystemTaskId) { 18 | if (id.type == ExternalSystemTaskType.RESOLVE_PROJECT) { 19 | // At this point changes might be still not applied to project structure yet. 20 | val project = id.findResolvedProject() ?: return 21 | BackgroundTaskUtil.runUnderDisposeAwareIndicator(this, Computable { 22 | for (module in project.modules.asList()) { 23 | module.getServiceIfCreated(BridgeModuleCacheService::class.java)?.initialized = false 24 | } 25 | }) 26 | } 27 | } 28 | } 29 | 30 | internal fun ExternalSystemTaskId.findResolvedProject(): Project? { 31 | if (type != ExternalSystemTaskType.RESOLVE_PROJECT) return null 32 | return findProject() 33 | } 34 | -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/BridgeStorageComponentContainerContributor.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide 2 | 3 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.resolve.hasJvmComponent 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeDeclarationChecker 5 | import org.jetbrains.kotlin.container.StorageComponentContainer 6 | import org.jetbrains.kotlin.container.useInstance 7 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 8 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 9 | import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor 10 | import org.jetbrains.kotlin.platform.TargetPlatform 11 | import org.jetbrains.kotlin.resolve.descriptorUtil.module 12 | 13 | 14 | class BridgeStorageComponentContainerContributor : StorageComponentContainerContributor { 15 | override fun registerModuleComponents( 16 | container: StorageComponentContainer, 17 | platform: TargetPlatform, 18 | moduleDescriptor: ModuleDescriptor, 19 | ) { 20 | // container.getService(JvmBlockingBridgeConfigurationService::class.java).ext = ext ?: error("Failed to get ext service") 21 | 22 | //container.useInstance(BridgeCodegenCliExtension()) 23 | 24 | if (!platform.hasJvmComponent()) return 25 | 26 | container.useInstance(object : 27 | BlockingBridgeDeclarationChecker({ it.bridgeConfiguration }) { 28 | override fun isPluginEnabled( 29 | descriptor: DeclarationDescriptor, 30 | ): Boolean { 31 | return descriptor.module.isBlockingBridgePluginEnabled() 32 | } 33 | }) 34 | } 35 | } -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/Icons.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide 2 | 3 | import com.intellij.openapi.util.IconLoader 4 | import javax.swing.Icon 5 | 6 | internal object Icons { 7 | val BridgedSuspendCall: Icon = IconLoader.getIcon("/icons/bridgedSuspendCall.svg", Icons::class.java) 8 | val BridgedSuspendCallDark: Icon = IconLoader.getIcon("/icons/bridgedSuspendCall_dark.svg", Icons::class.java) 9 | } -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/bridgeExt.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide 2 | 3 | import com.intellij.openapi.module.Module 4 | import com.intellij.psi.PsiElement 5 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.BridgeCommandLineProcessor 6 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.IBridgeConfiguration 7 | import me.him188.kotlin.jvm.blocking.bridge.compiler.extensions.createBridgeConfig 8 | import org.jetbrains.kotlin.analyzer.ModuleInfo 9 | import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments 10 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 11 | import org.jetbrains.kotlin.compiler.plugin.CliOption 12 | import org.jetbrains.kotlin.config.CompilerConfiguration 13 | import org.jetbrains.kotlin.config.KotlinFacetSettings 14 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 15 | import org.jetbrains.kotlin.idea.base.projectStructure.unwrapModuleSourceInfo 16 | import org.jetbrains.kotlin.idea.base.util.module 17 | import org.jetbrains.kotlin.idea.caches.project.toDescriptor 18 | import org.jetbrains.kotlin.idea.facet.KotlinFacet 19 | 20 | val Module.bridgeConfiguration 21 | get() = useBridgeCacheOrInit { it.config } ?: IBridgeConfiguration.Default 22 | 23 | val PsiElement.bridgeConfiguration 24 | get() = module?.bridgeConfiguration ?: IBridgeConfiguration.Default 25 | 26 | val Module.isBridgeCompilerEnabled 27 | get() = useBridgeCacheOrInit { it.compilerEnabled } ?: false 28 | 29 | val PsiElement.isBridgeCompilerEnabled 30 | get() = this.module?.isBridgeCompilerEnabled ?: false 31 | 32 | 33 | inline fun Module.useBridgeCacheOrInit( 34 | useCache: (cache: BridgeModuleCacheService) -> R, 35 | ): R? { 36 | val module = this 37 | val moduleDescriptor = module.toDescriptor() ?: return null 38 | val cache = module.getService(BridgeModuleCacheService::class.java) 39 | if (cache.initialized) { 40 | return useCache(cache) 41 | } 42 | 43 | cache.config = moduleDescriptor.createBridgeConfig() ?: IBridgeConfiguration.Default 44 | cache.compilerEnabled = moduleDescriptor.isBlockingBridgePluginEnabled() 45 | cache.initialized = true 46 | 47 | return useCache(cache) 48 | } 49 | 50 | // 51 | fun ModuleDescriptor.createBridgeConfig(): IBridgeConfiguration? { 52 | return kotlinFacetSettings()?.compilerArguments?.kjbbCompilerConfiguration()?.createBridgeConfig() 53 | } 54 | 55 | private fun CommonCompilerArguments.kjbbCompilerConfiguration(): CompilerConfiguration? { 56 | val pluginOptions = pluginOptions ?: return null 57 | 58 | fun findOption(option: CliOption): String? { 59 | return pluginOptions.find { it.startsWith("plugin:${BridgeCommandLineProcessor.COMPILER_PLUGIN_ID}:${option.optionName}=") } 60 | ?.substringAfter('=', "") 61 | } 62 | 63 | val processor = BridgeCommandLineProcessor() 64 | val configuration = CompilerConfiguration() 65 | 66 | for (pluginOption in processor.pluginOptions) { 67 | val find = findOption(pluginOption) 68 | if (find != null) { 69 | processor.processOption(pluginOption as AbstractCliOption, find, configuration) 70 | } 71 | } 72 | return configuration 73 | } 74 | 75 | fun ModuleDescriptor.kotlinFacetSettings(): KotlinFacetSettings? { 76 | val module = 77 | getCapability(ModuleInfo.Capability)?.unwrapModuleSourceInfo()?.module ?: return null 78 | val facet = KotlinFacet.get(module) ?: return null 79 | return facet.configuration.settings 80 | } 81 | 82 | 83 | fun ModuleDescriptor.isBlockingBridgePluginEnabled(): Boolean { 84 | // /.m2/repository/net/mamoe/kotlin-jvm-blocking-bridge-compiler-embeddable/1.4.0/kotlin-jvm-blocking-bridge-compiler-embeddable-1.4.0.jar 85 | val pluginJpsJarName = "kotlin-jvm-blocking-bridge-compiler" 86 | val module = 87 | getCapability(ModuleInfo.Capability)?.unwrapModuleSourceInfo()?.module 88 | ?: return false 89 | val facet = KotlinFacet.get(module) ?: return false 90 | val pluginClasspath = 91 | facet.configuration.settings.compilerArguments?.pluginClasspaths ?: return false 92 | 93 | if (pluginClasspath.none { path -> path.contains(pluginJpsJarName) }) return false 94 | return true 95 | } -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/fix/BlockingBridgeBundle.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide.fix 2 | 3 | import org.jetbrains.annotations.NonNls 4 | import org.jetbrains.annotations.PropertyKey 5 | import org.jetbrains.kotlin.util.AbstractKotlinBundle 6 | 7 | 8 | @NonNls 9 | private const val BUNDLE = "messages.BlockingBridgeBundle" 10 | 11 | object BlockingBridgeBundle : AbstractKotlinBundle(BUNDLE) { 12 | @JvmStatic 13 | fun message(@NonNls @PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String = 14 | getMessage(key, *params) 15 | 16 | @JvmStatic 17 | fun htmlMessage(@NonNls @PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String = 18 | getMessage(key, *params).withHtml() 19 | 20 | @JvmStatic 21 | fun lazyMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): () -> String = 22 | { getMessage(key, *params) } 23 | } 24 | -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/fix/QuickFixRegistrar.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide.fix 2 | 3 | import com.intellij.codeInsight.intention.IntentionAction 4 | import me.him188.kotlin.jvm.blocking.bridge.compiler.diagnostic.BlockingBridgeErrors.* 5 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory 6 | import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory 7 | import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor 8 | import org.jetbrains.kotlin.idea.quickfix.QuickFixes 9 | 10 | class QuickFixRegistrar : QuickFixContributor { 11 | override fun registerQuickFixes(quickFixes: QuickFixes) { 12 | fun DiagnosticFactory<*>.registerFactory(vararg factory: KotlinIntentionActionsFactory) { 13 | quickFixes.register(this, *factory) 14 | } 15 | 16 | @Suppress("unused") 17 | fun DiagnosticFactory<*>.registerActions(vararg action: IntentionAction) { 18 | quickFixes.register(this, *action) 19 | } 20 | 21 | REDUNDANT_JVM_BLOCKING_BRIDGE_WITH_JVM_SYNTHETIC 22 | .registerFactory(RemoveJvmBlockingBridgeFix, RemoveJvmSyntheticFix) 23 | 24 | REDUNDANT_JVM_BLOCKING_BRIDGE_ON_NON_PUBLIC_DECLARATIONS.registerFactory(RemoveJvmBlockingBridgeFix) 25 | 26 | INAPPLICABLE_JVM_BLOCKING_BRIDGE.registerFactory(RemoveJvmBlockingBridgeFix) 27 | INLINE_CLASSES_NOT_SUPPORTED.registerFactory(RemoveJvmBlockingBridgeFix) 28 | INTERFACE_NOT_SUPPORTED.registerFactory(RemoveJvmBlockingBridgeFix) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/fix/RemoveJvmBlockingBridgeFix.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide.fix 2 | 3 | import com.intellij.codeInsight.intention.IntentionAction 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.PsiFile 7 | import com.intellij.psi.util.parentsOfType 8 | import me.him188.kotlin.jvm.blocking.bridge.compiler.backend.ir.RuntimeIntrinsics 9 | import org.jetbrains.kotlin.diagnostics.Diagnostic 10 | import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix 11 | import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction 12 | import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory 13 | import org.jetbrains.kotlin.idea.util.findAnnotation 14 | import org.jetbrains.kotlin.psi.KtAnnotationEntry 15 | import org.jetbrains.kotlin.psi.KtFunction 16 | import org.jetbrains.kotlin.psi.KtModifierListOwner 17 | 18 | class RemoveJvmBlockingBridgeFix( 19 | element: KtFunction, 20 | ) : KotlinCrossLanguageQuickFixAction(element), KotlinUniversalQuickFix { 21 | 22 | override fun getFamilyName(): String = BlockingBridgeBundle.message("remove.jvm.blocking.bridge.fix") 23 | override fun getText(): String = BlockingBridgeBundle.message("remove.jvm.blocking.bridge") 24 | 25 | override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { 26 | element?.findAnnotation(RuntimeIntrinsics.JvmBlockingBridgeFqName)?.delete() ?: return 27 | } 28 | 29 | companion object : KotlinSingleIntentionActionFactory() { 30 | override fun createAction(diagnostic: Diagnostic): IntentionAction? { 31 | val target = 32 | (diagnostic.psiElement as? KtAnnotationEntry)?.parentsOfType() 33 | ?.firstOrNull() 34 | ?: return null 35 | return RemoveJvmBlockingBridgeFix(target) 36 | } 37 | 38 | override fun isApplicableForCodeFragment(): Boolean = false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/fix/RemoveJvmSyntheticFix.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide.fix 2 | 3 | import com.intellij.codeInsight.intention.IntentionAction 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.PsiFile 7 | import com.intellij.psi.util.parentsOfType 8 | import org.jetbrains.kotlin.diagnostics.Diagnostic 9 | import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix 10 | import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction 11 | import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory 12 | import org.jetbrains.kotlin.idea.util.findAnnotation 13 | import org.jetbrains.kotlin.psi.KtAnnotationEntry 14 | import org.jetbrains.kotlin.psi.KtFunction 15 | import org.jetbrains.kotlin.psi.KtModifierListOwner 16 | import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME 17 | 18 | class RemoveJvmSyntheticFix( 19 | element: KtFunction, 20 | ) : KotlinCrossLanguageQuickFixAction(element), KotlinUniversalQuickFix { 21 | 22 | override fun getFamilyName(): String = BlockingBridgeBundle.message("remove.jvm.synthetic.fix") 23 | override fun getText(): String = BlockingBridgeBundle.message("remove.jvm.synthetic") 24 | 25 | override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { 26 | element?.findAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME)?.delete() ?: return 27 | } 28 | 29 | companion object : KotlinSingleIntentionActionFactory() { 30 | override fun createAction(diagnostic: Diagnostic): IntentionAction? { 31 | val target = 32 | (diagnostic.psiElement as? KtAnnotationEntry)?.parentsOfType() 33 | ?.firstOrNull() 34 | ?: return null 35 | return RemoveJvmSyntheticFix(target) 36 | } 37 | 38 | override fun isApplicableForCodeFragment(): Boolean = false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ide-plugin/src/main/kotlin/me/him188/kotlin/jvm/blocking/bridge/ide/line/marker/BlockingBridgeLineMarkerProvider.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge.ide.line.marker 2 | 3 | import com.intellij.codeInsight.daemon.LineMarkerInfo 4 | import com.intellij.codeInsight.daemon.LineMarkerProvider 5 | import com.intellij.openapi.actionSystem.AnAction 6 | import com.intellij.openapi.editor.markup.GutterIconRenderer 7 | import com.intellij.openapi.progress.ProgressManager 8 | import com.intellij.psi.* 9 | import me.him188.kotlin.jvm.blocking.bridge.ide.BlockingBridgeStubMethod 10 | import me.him188.kotlin.jvm.blocking.bridge.ide.Icons 11 | import org.jetbrains.kotlin.psi.KtForExpression 12 | import org.jetbrains.kotlin.psi.KtSimpleNameExpression 13 | import org.jetbrains.kotlin.psi.psiUtil.endOffset 14 | import org.jetbrains.kotlin.psi.psiUtil.startOffset 15 | 16 | class BlockingBridgeLineMarkerProvider : LineMarkerProvider { 17 | override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { 18 | return null 19 | } 20 | 21 | override fun collectSlowLineMarkers( 22 | elements: MutableList, 23 | result: MutableCollection> 24 | ) { 25 | val markedLineNumbers = HashSet() 26 | 27 | for (element in elements) { 28 | ProgressManager.checkCanceled() 29 | 30 | if (element !is PsiReferenceExpression) continue 31 | 32 | val containingFile = element.containingFile 33 | if (containingFile !is PsiJavaFile || containingFile is PsiJavaCodeReferenceCodeFragment) { 34 | continue 35 | } 36 | 37 | val lineNumber = element.getLineNumber() 38 | if (lineNumber in markedLineNumbers) continue 39 | if (!element.hasBridgeCalls()) continue 40 | 41 | 42 | markedLineNumbers += lineNumber 43 | result += if (element is KtForExpression) { 44 | BridgeCallLineMarkerInfo( 45 | getElementForLineMark(element.loopRange!!), 46 | // KotlinBundle.message("highlighter.message.suspending.iteration") 47 | ) 48 | } else { 49 | BridgeCallLineMarkerInfo( 50 | getElementForLineMark(element), 51 | //KotlinBundle.message("highlighter.message.suspend.function.call") 52 | ) 53 | } 54 | } 55 | } 56 | 57 | class BridgeCallLineMarkerInfo( 58 | callElement: PsiElement, 59 | ) : LineMarkerInfo( 60 | callElement, 61 | callElement.textRange, 62 | Icons.BridgedSuspendCall, 63 | { 64 | "Blocking bridge method call" 65 | }, 66 | null, 67 | GutterIconRenderer.Alignment.RIGHT, 68 | { "Blocking bridge method call" } 69 | ) { 70 | override fun createGutterRenderer(): GutterIconRenderer { 71 | return object : LineMarkerGutterIconRenderer(this) { 72 | override fun getClickAction(): AnAction? = null 73 | } 74 | } 75 | } 76 | 77 | } 78 | 79 | fun PsiReferenceExpression.hasBridgeCalls(): Boolean { 80 | val resolved = this.resolve() as? PsiMethod ?: return false 81 | 82 | if (resolved is BlockingBridgeStubMethod) return true 83 | 84 | return false // resolved.canHaveBridgeFunctions() 85 | } 86 | 87 | val PsiElement.document 88 | get() = containingFile.viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(containingFile) 89 | 90 | fun PsiElement.getLineNumber(start: Boolean = true): Int { 91 | val document = document 92 | val index = if (start) this.startOffset else this.endOffset 93 | if (index > (document?.textLength ?: 0)) return 0 94 | return document?.getLineNumber(index) ?: 0 95 | } 96 | 97 | internal fun getElementForLineMark(callElement: PsiElement): PsiElement = 98 | when (callElement) { 99 | is KtSimpleNameExpression -> callElement.getReferencedNameElement() 100 | else -> 101 | // a fallback, 102 | //but who knows what to reference in KtArrayAccessExpression ? 103 | generateSequence(callElement) { it.firstChild }.last() 104 | } -------------------------------------------------------------------------------- /ide-plugin/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | net.mamoe.kotlin-jvm-blocking-bridge 3 | 4 | Kotlin JVM Blocking Bridge 5 | 6 | 9 | Tianyi Guan 10 | 11 | 12 | Kotlin compiler plugin for generating blocking bridges for calling suspend functions from Java with minimal effort

14 |

Usage

15 |

To use this plugin, you also need to install a compiler plugin to your Kotlin compiler. See details on GitHub

16 | 17 |

Screenshots

18 | 19 |

Motivation

20 |

Kotlin suspend function is compiled with an additional $completion: Continuation parameter, making it hard to call from Java. To help integration with Java, we may make extra effort to simplify calling:

21 |
suspend fun downloadImage(): Image
22 |         
23 |

We can add

24 |
@JvmName("downloadImage") // avoid resolution ambiguity
25 |             fun downloadImageBlocking(): Image = runBlocking { downloadImage() }
26 |         
27 |

so Java users can also call downloadImage() just like calling the suspend function, without implementing a Continuation.

28 |

However, there several problems:

29 |
    30 |
  • KDoc is copied to the bridge, when updating, copying is also required.
  • 31 |
  • Changing the signature becomes inconvenient.
  • 32 |
  • downloadImageBlocking is also exposed to Kotlin callers, and we can't hide them. We can make it 'difficult' to call by adding RequiresOptIn 33 |
    @RequiresOptIn(level = ERROR)
    34 |                     annotation class JavaFriendlyApi
    35 | 
    36 |                     @JavaFriendlyApi // so IDE reports 'Experimental API usage' error for calling from Kotlin.
    37 |                     @JvmName("downloadImage") // avoid resolution ambiguity
    38 |                     fun downloadImageBlocking(): Image = runBlocking { downloadImage() }
    39 |                 
    40 |
  • 41 |
42 |

This plugin has been designed to minimize work against Java compatibility, to provide the ability to call Kotlin's suspend function in a 'blocking' way:

43 |
@JvmBlockingBridge
44 |             suspend fun downloadImage(): Image
45 |         
46 |

The Kotlin JVM Blocking Bridge compiler will generate such blocking bridges automatically.

47 |

Stability

48 |

There are more than 150 unit tests ensuring the functioning of this plugin.

49 |

This compiler plugin has been used all over the library mirai, which consists of 87k lines of code, covers all the circumstances you may use this plugin for, and has been used by thousand of customers.
50 | This means that Kotlin Jvm Blocking Bridge produces high stability and is capable for production use.

51 |

Usage

52 |

To use this plugin, you also need to install a compiler plugin to your Kotlin compiler. See details on GitHub

53 | ]]> 54 |
55 | 56 | com.intellij.modules.platform 57 | org.jetbrains.kotlin 58 | com.intellij.modules.java 59 | 60 | 61 | 63 | 65 | 66 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 86 |
-------------------------------------------------------------------------------- /ide-plugin/src/main/resources/icons/bridgedSuspendCall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ide-plugin/src/main/resources/icons/bridgedSuspendCall_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ide-plugin/src/main/resources/messages/BlockingBridgeBundle.properties: -------------------------------------------------------------------------------- 1 | remove.jvm.blocking.bridge.fix=Remove JvmBlockingBridge 2 | remove.jvm.blocking.bridge=Remove @JvmBlockingBridge 3 | remove.jvm.synthetic.fix=Remove JvmSynthetic 4 | remove.jvm.synthetic=Remove @JvmSynthetic 5 | blocking.bridge.hints=Blocking Bridge Hints -------------------------------------------------------------------------------- /runtime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNUSED_VARIABLE") 2 | 3 | import org.apache.tools.ant.taskdefs.condition.Os 4 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 5 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 6 | 7 | plugins { 8 | id("me.him188.maven-central-publish") 9 | kotlin("multiplatform") 10 | kotlin("plugin.serialization") 11 | } 12 | 13 | kotlin { 14 | explicitApi() 15 | 16 | 17 | jvm() 18 | js { 19 | useCommonJs() 20 | } 21 | 22 | 23 | val ideaActive = System.getProperty("idea.active") == "true" 24 | 25 | val nativeMainSets = mutableListOf() 26 | val nativeTestSets = mutableListOf() 27 | 28 | if (ideaActive) { 29 | when { 30 | Os.isFamily(Os.FAMILY_MAC) -> if (Os.isArch("aarch64")) macosArm64("native") else macosX64("native") 31 | Os.isFamily(Os.FAMILY_WINDOWS) -> mingwX64("native") 32 | else -> linuxX64("native") 33 | } 34 | } else { 35 | // 1.6.0 36 | val nativeTargets = arrayOf( 37 | "androidNativeArm32, androidNativeArm64, androidNativeX86, androidNativeX64", 38 | "iosArm32, iosArm64, iosX64, iosSimulatorArm64", 39 | "watchosArm32, watchosArm64, watchosX86, watchosX64, watchosSimulatorArm64", 40 | "tvosArm64, tvosX64, tvosSimulatorArm64", 41 | "macosX64, macosArm64", 42 | "linuxArm64, linuxArm32Hfp, linuxMips32, linuxMipsel32, linuxX64", 43 | "mingwX64, mingwX86", 44 | "wasm32" 45 | ).flatMap { it.split(", ") } 46 | presets.filter { it.name in nativeTargets } 47 | .forEach { preset -> 48 | val target = targetFromPreset(preset, preset.name) 49 | nativeMainSets.add(target.compilations[KotlinCompilation.MAIN_COMPILATION_NAME].kotlinSourceSets.first()) 50 | nativeTestSets.add(target.compilations[KotlinCompilation.TEST_COMPILATION_NAME].kotlinSourceSets.first()) 51 | } 52 | } 53 | 54 | sourceSets { 55 | val commonMain by getting 56 | val commonTest by getting 57 | val jvmMain by getting 58 | val jvmTest by getting { 59 | dependencies { 60 | implementation("org.junit.jupiter:junit-jupiter-api:5.2.0") 61 | implementation("org.junit.jupiter:junit-jupiter-engine:5.2.0") 62 | } 63 | } 64 | 65 | val jsMain by getting 66 | 67 | if (!ideaActive) { 68 | configure(nativeMainSets) { 69 | dependsOn(sourceSets.maybeCreate("nativeMain")) 70 | } 71 | 72 | configure(nativeTestSets) { 73 | dependsOn(sourceSets.maybeCreate("nativeTest")) 74 | } 75 | } 76 | } 77 | } 78 | 79 | mavenCentralPublish { 80 | this.workingDir = rootProject.buildDir.resolve("temp/pub/").apply { mkdirs() } 81 | useCentralS01() 82 | singleDevGithubProject("Him188", "kotlin-jvm-blocking-bridge") 83 | licenseApacheV2() 84 | 85 | publishPlatformArtifactsInRootModule = "jvm" 86 | } 87 | 88 | kotlin.targets.asSequence() 89 | .flatMap { it.compilations } 90 | .filter { it.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm } 91 | .map { it.kotlinOptions } 92 | .filterIsInstance() 93 | .forEach { it.jvmTarget = "1.8" } 94 | 95 | java { 96 | sourceCompatibility = JavaVersion.VERSION_1_8 97 | targetCompatibility = JavaVersion.VERSION_1_8 98 | } 99 | -------------------------------------------------------------------------------- /runtime/src/commonMain/kotlin/me/him188/kotlin/jvm/blocking/bridge/JvmBlockingBridge.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalMultiplatform::class) 2 | 3 | package me.him188.kotlin.jvm.blocking.bridge 4 | 5 | import kotlin.jvm.JvmOverloads 6 | import kotlin.jvm.JvmStatic 7 | 8 | /** 9 | * Instructs the compiler to generate a blocking bridge for calling suspend function from Java. 10 | * 11 | * [JvmOverloads] and [JvmStatic] are supported. 12 | * 13 | * Example: 14 | * ``` 15 | * @JvmBlockingBridge 16 | * suspend fun foo( params ) { /* ... */ } 17 | * 18 | * // The compiler generates (visible only from Java): 19 | * @GeneratedBlockingBridge 20 | * fun foo( params ) = `$runSuspend$` { foo(params) } 21 | * ``` 22 | */ 23 | @OptionalExpectation 24 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.FILE) 25 | @Retention(AnnotationRetention.BINARY) 26 | public expect annotation class JvmBlockingBridge() -------------------------------------------------------------------------------- /runtime/src/commonMain/kotlin/net/mamoe/kjbb/JvmBlockingBridge.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalMultiplatform::class) 2 | 3 | package net.mamoe.kjbb 4 | 5 | import kotlin.jvm.JvmOverloads 6 | import kotlin.jvm.JvmStatic 7 | 8 | /** 9 | * Instructs the compiler to generate a blocking bridge for calling suspend function from Java. 10 | * 11 | * [JvmOverloads] and [JvmStatic] are supported. 12 | * 13 | * Example: 14 | * ``` 15 | * @JvmBlockingBridge 16 | * suspend fun foo( params ) { /* ... */ } 17 | * 18 | * // The compiler generates (visible only from Java): 19 | * @GeneratedBlockingBridge 20 | * fun foo( params ) = `$runSuspend$` { foo(params) } 21 | * ``` 22 | */ 23 | @OptionalExpectation 24 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) 25 | @Retention(AnnotationRetention.BINARY) 26 | @Deprecated( 27 | "Moved to me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge", 28 | level = DeprecationLevel.ERROR, 29 | replaceWith = ReplaceWith("JvmBlockingBridge", "me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge") 30 | ) 31 | public expect annotation class JvmBlockingBridge() -------------------------------------------------------------------------------- /runtime/src/jvmMain/kotlin/me/him188/kotlin/jvm/blocking/bridge/GeneratedBlockingBridge.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge 2 | 3 | 4 | private const val message = "This is generated to help Java callers, don't use in Kotlin." 5 | 6 | /** 7 | * The annotation that is added to the generated JVM blocking bridges by the compiler 8 | * to help IntelliJ plugin to hide members correctly. 9 | */ 10 | @Deprecated(message, level = DeprecationLevel.HIDDEN) 11 | @RequiresOptIn(message, level = RequiresOptIn.Level.ERROR) 12 | internal annotation class GeneratedBlockingBridge -------------------------------------------------------------------------------- /runtime/src/jvmMain/kotlin/me/him188/kotlin/jvm/blocking/bridge/JvmBlockingBridge.kt: -------------------------------------------------------------------------------- 1 | package me.him188.kotlin.jvm.blocking.bridge 2 | 3 | /** 4 | * Instructs the compiler to generate a blocking bridge for calling suspend function from Java. 5 | * 6 | * [JvmOverloads] and [JvmStatic] are supported. 7 | * 8 | * Example: 9 | * ``` 10 | * @JvmBlockingBridge 11 | * suspend fun foo( params ) { /* ... */ } 12 | * 13 | * // The compiler generates (visible only from Java): 14 | * @GeneratedBlockingBridge 15 | * fun foo( params ) = `$runSuspend$` { foo(params) } 16 | * ``` 17 | */ 18 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.FILE) 19 | @Retention(AnnotationRetention.BINARY) 20 | public actual annotation class JvmBlockingBridge -------------------------------------------------------------------------------- /runtime/src/jvmMain/kotlin/me/him188/kotlin/jvm/blocking/bridge/internal/RunSuspend.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "FunctionName", "unused") 2 | @file:JvmName("RunSuspendKt") 3 | 4 | package me.him188.kotlin.jvm.blocking.bridge.internal 5 | 6 | import kotlin.coroutines.Continuation 7 | import kotlin.coroutines.CoroutineContext 8 | import kotlin.coroutines.EmptyCoroutineContext 9 | import kotlin.coroutines.startCoroutine 10 | 11 | /** 12 | * me/him188/kotlin/jvm/blocking/bridge/internal/RunSuspendKt.runSuspend 13 | */ 14 | @Deprecated("For compiler use only", level = DeprecationLevel.HIDDEN) 15 | // don't internal, otherwise function name will be changed 16 | public fun `$runSuspend$`(block: suspend () -> Any?): Any? { 17 | val run = RunSuspend() 18 | block.startCoroutine(run) 19 | return run.await() 20 | } 21 | 22 | internal class RunSuspend : Continuation { 23 | override val context: CoroutineContext 24 | get() = EmptyCoroutineContext 25 | 26 | @Suppress("RESULT_CLASS_IN_RETURN_TYPE") 27 | var result: Result? = null 28 | 29 | override fun resumeWith(result: Result) = synchronized(this) { 30 | this.result = result 31 | (this as Object).notifyAll() 32 | } 33 | 34 | fun await(): R { 35 | synchronized(this) { 36 | while (true) { 37 | when (val result = this.result) { 38 | null -> (this as Object).wait() 39 | else -> { 40 | return result.getOrThrow() 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /runtime/src/jvmMain/kotlin/net/mamoe/kjbb/JvmBlockingBridge.kt: -------------------------------------------------------------------------------- 1 | package net.mamoe.kjbb 2 | 3 | /** 4 | * Instructs the compiler to generate a blocking bridge for calling suspend function from Java. 5 | * 6 | * [JvmOverloads] and [JvmStatic] are supported. 7 | * 8 | * Example: 9 | * ``` 10 | * @JvmBlockingBridge 11 | * suspend fun foo( params ) { /* ... */ } 12 | * 13 | * // The compiler generates (visible only from Java): 14 | * @GeneratedBlockingBridge 15 | * fun foo( params ) = `$runSuspend$` { foo(params) } 16 | * ``` 17 | */ 18 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.FILE) 19 | @Retention(AnnotationRetention.BINARY) 20 | @Deprecated( 21 | "Moved to me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge", 22 | level = DeprecationLevel.ERROR, 23 | replaceWith = ReplaceWith("JvmBlockingBridge", "me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge") 24 | ) 25 | public actual annotation class JvmBlockingBridge actual constructor() -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven("https://oss.sonatype.org/content/repositories/snapshots/") 5 | mavenLocal() 6 | } 7 | } 8 | 9 | rootProject.name = "kotlin-jvm-blocking-bridge" 10 | 11 | includeProject("kotlin-jvm-blocking-bridge-runtime", "runtime") 12 | includeProject("kotlin-jvm-blocking-bridge-compiler", "compiler-plugin") 13 | includeProject("kotlin-jvm-blocking-bridge-compiler-embeddable", "compiler-plugin-embeddable") 14 | includeProject("kotlin-jvm-blocking-bridge-gradle", "gradle-plugin") 15 | includeProject("kotlin-jvm-blocking-bridge-intellij", "ide-plugin") 16 | 17 | fun includeProject(name: String, path: String) { 18 | include(path) 19 | project(":$path").name = name 20 | } --------------------------------------------------------------------------------