├── .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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
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 | 
15 |
16 | 阻塞式方法桥调用:
17 | 
18 |
19 | 文档和跳转支持:
20 | 
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 super Unit> $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 super Unit>) 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 |
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 | }
--------------------------------------------------------------------------------