├── .github ├── actions │ ├── cache-konan │ │ └── action.yaml │ └── publish-spm-tag │ │ └── action.yaml └── workflows │ ├── code-quality.yaml │ ├── release-idea.yaml │ ├── release-kotlin-preview.yaml │ ├── release-kotlin.yaml │ ├── release-swift.yaml │ ├── run-idea-tests.yaml │ ├── run-integration-tests.yaml │ ├── run-kotlin-tests.yaml │ ├── run-swift-tests.yaml │ └── run-tests.yaml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── kotlinc.xml ├── libraries-with-intellij-classes.xml ├── misc.xml ├── runConfigurations │ ├── Compiler_Tests.xml │ ├── Core_Tests.xml │ ├── KSP_Tests.xml │ ├── Run_IDE.xml │ └── Verify_Plugin.xml └── vcs.xml ├── KMPNativeCoroutines.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── KMPNativeCoroutinesAsync.xcscheme │ ├── KMPNativeCoroutinesCombine.xcscheme │ ├── KMPNativeCoroutinesCore.xcscheme │ └── KMPNativeCoroutinesRxSwift.xcscheme ├── KMPNativeCoroutinesAsync.podspec ├── KMPNativeCoroutinesAsync ├── AsyncError.swift ├── AsyncFunction.swift ├── AsyncResult.swift └── AsyncSequence.swift ├── KMPNativeCoroutinesAsyncTests ├── AsyncFunctionTests.swift ├── AsyncResultTests.swift └── AsyncSequenceTests.swift ├── KMPNativeCoroutinesCombine.podspec ├── KMPNativeCoroutinesCombine ├── Future.swift ├── FuturePublisher.swift └── Publisher.swift ├── KMPNativeCoroutinesCombineTests ├── FutureTests.swift └── PublisherTests.swift ├── KMPNativeCoroutinesCore.podspec ├── KMPNativeCoroutinesCore ├── NativeCallback.swift ├── NativeCancellable.swift ├── NativeFlow.swift └── NativeSuspend.swift ├── KMPNativeCoroutinesRxSwift.podspec ├── KMPNativeCoroutinesRxSwift ├── Observable.swift ├── Single.swift └── SingleObservable.swift ├── KMPNativeCoroutinesRxSwiftTests ├── ObservableTests.swift └── SingleTests.swift ├── LICENSE.txt ├── MIGRATING_TO_V1.md ├── Package.swift ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── BuildType.kt │ ├── kmp-nativecoroutines-kotlin-jvm.gradle.kts │ ├── kmp-nativecoroutines-kotlin-multiplatform.gradle.kts │ └── kmp-nativecoroutines-publish.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kmp-nativecoroutines-annotations ├── api │ ├── kmp-nativecoroutines-annotations.api │ └── kmp-nativecoroutines-annotations.klib.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ ├── NativeCoroutineScope.kt │ │ ├── NativeCoroutines.kt │ │ ├── NativeCoroutinesIgnore.kt │ │ ├── NativeCoroutinesRefined.kt │ │ ├── NativeCoroutinesRefinedState.kt │ │ └── NativeCoroutinesState.kt │ └── jvmMain │ └── resources │ └── META-INF │ └── com │ └── rickclephas │ └── kmp │ └── kmp-nativecoroutines-annotations-jvm │ └── verification.properties ├── kmp-nativecoroutines-compiler-embeddable ├── .gitignore ├── api │ └── kmp-nativecoroutines-compiler-embeddable.api └── build.gradle.kts ├── kmp-nativecoroutines-compiler ├── api │ └── kmp-nativecoroutines-compiler.api ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── rickclephas │ │ │ └── kmp │ │ │ └── nativecoroutines │ │ │ └── compiler │ │ │ ├── KmpNativeCoroutinesCommandLineProcessor.kt │ │ │ ├── KmpNativeCoroutinesCompilerPluginRegistrar.kt │ │ │ ├── classic │ │ │ ├── diagnostics │ │ │ │ ├── DefaultErrorMessages.kt │ │ │ │ ├── KmpNativeCoroutinesChecker.kt │ │ │ │ └── KmpNativeCoroutinesErrors.kt │ │ │ ├── extensions │ │ │ │ └── KmpNativeCoroutinesStorageComponentContainerContributor.kt │ │ │ └── utils │ │ │ │ ├── CoroutinesReturnType.kt │ │ │ │ ├── ImplicitReturnType.kt │ │ │ │ ├── NativeCoroutinesAnnotations.kt │ │ │ │ └── ObjCRefinement.kt │ │ │ ├── config │ │ │ ├── CompilerConfiguration.kt │ │ │ ├── ConfigListOption.kt │ │ │ ├── ConfigOption.kt │ │ │ ├── ExposedSeverity.kt │ │ │ ├── GeneratedSourceDir.kt │ │ │ ├── K2Mode.kt │ │ │ └── Suffixes.kt │ │ │ ├── fir │ │ │ ├── codegen │ │ │ │ ├── Annotations.kt │ │ │ │ ├── CallableReferenceBlock.kt │ │ │ │ ├── NativeFunction.kt │ │ │ │ ├── NativeProperty.kt │ │ │ │ ├── PropertyAccessor.kt │ │ │ │ ├── ReceiverParameter.kt │ │ │ │ ├── SharedFlowReplayCache.kt │ │ │ │ ├── StateFlowValue.kt │ │ │ │ ├── TodoCall.kt │ │ │ │ ├── TypeParameters.kt │ │ │ │ └── ValueParameters.kt │ │ │ ├── diagnostics │ │ │ │ ├── FirDefaultErrorMessages.kt │ │ │ │ ├── FirKmpNativeCoroutinesDeclarationChecker.kt │ │ │ │ └── FirKmpNativeCoroutinesErrors.kt │ │ │ ├── extensions │ │ │ │ ├── KmpNativeCoroutinesDeclarationGenerationExtension.kt │ │ │ │ ├── KmpNativeCoroutinesFirAdditionalCheckersExtension.kt │ │ │ │ └── KmpNativeCoroutinesFirExtensionRegistrar.kt │ │ │ └── utils │ │ │ │ ├── CoroutinesReturnType.kt │ │ │ │ ├── FirCallableSignature.kt │ │ │ │ ├── FirDeclarationStatus.kt │ │ │ │ ├── FirLiteralExpression.kt │ │ │ │ ├── FirResolvedTypeRef.kt │ │ │ │ ├── ImplicitReturnType.kt │ │ │ │ ├── NativeConeKotlinType.kt │ │ │ │ ├── NativeCoroutinesAnnotations.kt │ │ │ │ ├── NativeCoroutinesDeclarationKey.kt │ │ │ │ └── ObjCRefinement.kt │ │ │ ├── ir │ │ │ ├── codegen │ │ │ │ ├── CoroutineScope.kt │ │ │ │ ├── GeneratorContext.kt │ │ │ │ ├── Lambda.kt │ │ │ │ ├── NativeFlow.kt │ │ │ │ ├── NativeFunction.kt │ │ │ │ ├── NativeProperty.kt │ │ │ │ ├── NativeSuspend.kt │ │ │ │ ├── OriginalFunction.kt │ │ │ │ ├── OriginalProperty.kt │ │ │ │ ├── SharedFlowReplayCache.kt │ │ │ │ └── StateFlowValue.kt │ │ │ ├── extensions │ │ │ │ ├── KmpNativeCoroutinesIrGenerationExtension.kt │ │ │ │ └── KmpNativeCoroutinesIrTransformer.kt │ │ │ └── utils │ │ │ │ ├── FlowValueTypeArg.kt │ │ │ │ └── IrBlockBodyExpression.kt │ │ │ └── utils │ │ │ ├── CallableIds.kt │ │ │ ├── CallableSignature.kt │ │ │ ├── ClassIds.kt │ │ │ ├── CoroutinesReturnType.kt │ │ │ ├── FqNames.kt │ │ │ ├── NameSuffix.kt │ │ │ ├── Names.kt │ │ │ └── NativeCoroutinesAnnotation.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ ├── org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor │ │ └── org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar │ ├── test │ ├── generated │ │ └── com │ │ │ └── rickclephas │ │ │ └── kmp │ │ │ └── nativecoroutines │ │ │ └── compiler │ │ │ └── runners │ │ │ ├── ClassicDiagnosticsTestGenerated.java │ │ │ ├── FirLightTreeCodegenTestGenerated.java │ │ │ ├── FirLightTreeDiagnosticsTestGenerated.java │ │ │ ├── FirPsiCodegenTestGenerated.java │ │ │ └── FirPsiDiagnosticsTestGenerated.java │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ └── compiler │ │ ├── GenerateTests.kt │ │ ├── directives │ │ └── KmpNativeCoroutinesDirectives.kt │ │ ├── runners │ │ ├── AbstractBaseDiagnosticsTest.kt │ │ ├── AbstractClassicDiagnosticsTest.kt │ │ ├── AbstractFirBaseCodegenTest.kt │ │ └── AbstractFirDiagnosticsTest.kt │ │ └── services │ │ ├── KmpNativeCoroutinesCompilerPluginConfigurator.kt │ │ └── KmpNativeCoroutinesRuntimeClasspathProvider.kt │ └── testData │ ├── codegen │ ├── annotations.box.txt │ ├── annotations.fir.ir.txt │ ├── annotations.fir.kt.txt │ ├── annotations.fir.txt │ ├── annotations.kt │ ├── coroutinescope.box.txt │ ├── coroutinescope.fir.ir.txt │ ├── coroutinescope.fir.kt.txt │ ├── coroutinescope.fir.txt │ ├── coroutinescope.kt │ ├── functions.box.txt │ ├── functions.fir.ir.txt │ ├── functions.fir.kt.txt │ ├── functions.fir.txt │ ├── functions.kt │ ├── properties.box.txt │ ├── properties.fir.ir.txt │ ├── properties.fir.kt.txt │ ├── properties.fir.txt │ ├── properties.kt │ ├── viewmodelscope.box.txt │ ├── viewmodelscope.fir.ir.txt │ ├── viewmodelscope.fir.kt.txt │ ├── viewmodelscope.fir.txt │ └── viewmodelscope.kt │ └── diagnostics │ ├── conflict.kt │ ├── exposedAnnotated.kt │ ├── exposedError.kt │ ├── exposedNone.kt │ ├── exposedWarning.kt │ ├── ignored.kt │ ├── implicitReturnTypeK2.kt │ ├── implicitReturnTypeKSP.kt │ ├── incompatible.fir.kt │ ├── incompatible.kt │ ├── invalid.kt │ ├── redundant.kt │ └── unsupported.kt ├── kmp-nativecoroutines-core ├── api │ ├── kmp-nativecoroutines-core.api │ └── kmp-nativecoroutines-core.klib.api ├── build.gradle.kts └── src │ ├── appleMain │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ └── NativeErrorApple.kt │ ├── appleTest │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ ├── KotlinCauseApple.kt │ │ └── NativeErrorAppleTests.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ └── Empty.kt │ ├── commonTest │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ ├── RandomException.kt │ │ └── RandomValue.kt │ ├── compilerTestMain │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ ├── BoxTest.kt │ │ └── NativeErrorJvm.kt │ ├── compilerTestTest │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ └── KotlinCauseJvm.kt │ ├── jvmMain │ └── resources │ │ └── META-INF │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── kmp-nativecoroutines-core-jvm │ │ └── verification.properties │ ├── nativeCoroutinesMain │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ ├── CoroutineScope.kt │ │ ├── NativeCallback.kt │ │ ├── NativeCancellable.kt │ │ ├── NativeError.kt │ │ ├── NativeFlow.kt │ │ └── NativeSuspend.kt │ └── nativeCoroutinesTest │ └── kotlin │ └── com │ └── rickclephas │ └── kmp │ └── nativecoroutines │ ├── KotlinCause.kt │ ├── NativeCallbackTests.kt │ ├── NativeCancellableTests.kt │ ├── NativeFlowTests.kt │ └── NativeSuspendTests.kt ├── kmp-nativecoroutines-gradle-plugin ├── Version.kt ├── api │ └── kmp-nativecoroutines-gradle-plugin.api ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── rickclephas │ └── kmp │ └── nativecoroutines │ └── gradle │ ├── KmpNativeCoroutinesExtension.kt │ ├── KmpNativeCoroutinesPlugin.kt │ └── KspCommandLineArgumentProvider.kt ├── kmp-nativecoroutines-idea-plugin ├── api │ └── kmp-nativecoroutines-idea-plugin.api ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ ├── kotlin │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ └── idea │ │ ├── compiler │ │ └── extensions │ │ │ ├── KmpNativeCoroutinesCompilerPluginProvider.kt │ │ │ └── KmpNativeCoroutinesStorageComponentContainerContributor.kt │ │ ├── gradle │ │ ├── KmpNativeCoroutinesGradleProjectImportHandler.kt │ │ ├── KmpNativeCoroutinesModel.kt │ │ ├── KmpNativeCoroutinesModelBuilderService.kt │ │ └── KmpNativeCoroutinesProjectResolverExtension.kt │ │ └── quickfixes │ │ ├── k1 │ │ ├── AddAnnotationFixFactory.kt │ │ ├── KmpNativeCoroutinesQuickFixContributor.kt │ │ └── RemoveAnnotationFixFactory.kt │ │ └── k2 │ │ ├── AddAnnotationFixFactory.kt │ │ ├── KmpNativeCoroutinesQuickFixRegistrar.kt │ │ └── RemoveAnnotationFixFactory.kt │ └── resources │ └── META-INF │ ├── plugin.xml │ └── services │ └── org.jetbrains.plugins.gradle.tooling.ModelBuilderService ├── kmp-nativecoroutines-ksp ├── api │ └── kmp-nativecoroutines-ksp.api ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── rickclephas │ │ │ └── kmp │ │ │ └── nativecoroutines │ │ │ └── ksp │ │ │ ├── AnnotationSpecs.kt │ │ │ ├── CoroutineScopeProvider.kt │ │ │ ├── KmpNativeCoroutinesOptions.kt │ │ │ ├── KmpNativeCoroutinesSymbolProcessor.kt │ │ │ ├── KmpNativeCoroutinesSymbolProcessorProvider.kt │ │ │ ├── Names.kt │ │ │ ├── NativeCoroutinesFunSpec.kt │ │ │ ├── NativeCoroutinesPropertySpecs.kt │ │ │ ├── ReturnType.kt │ │ │ └── kotlinpoet │ │ │ ├── ParameterSpec.kt │ │ │ ├── TypeName.kt │ │ │ ├── TypeParameterResolver.kt │ │ │ └── TypeVariableName.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider │ └── test │ └── kotlin │ └── com │ └── rickclephas │ └── kmp │ └── nativecoroutines │ └── ksp │ ├── CompilationTests.kt │ ├── CoroutineScopeProviderTests.kt │ ├── NativeCoroutinesFunSpecTests.kt │ └── NativeCoroutinesPropertySpecsTests.kt ├── kotlin-js-store └── yarn.lock ├── qodana.yaml ├── sample ├── .gitignore ├── Async │ ├── AsyncFunctionIntegrationTests.swift │ ├── AsyncResultIntegrationTests.swift │ ├── AsyncSequenceIntegrationTests.swift │ ├── AsyncTestUtils.swift │ ├── ClockAsyncViewModel.swift │ ├── RandomLettersAsyncViewModel.swift │ └── SwiftUIAsyncTest.swift ├── Combine │ ├── ClockCombineViewModel.swift │ ├── CombineFutureIntegrationTests.swift │ ├── CombinePublisherIntegrationTests.swift │ └── RandomLettersCombineViewModel.swift ├── IntegrationTests │ ├── CompilerIntegrationTests.swift │ ├── NewMemoryModelIntegrationTests.swift │ └── TestUtils.swift ├── Issues │ ├── GH206Tests.swift │ └── GH219Tests.swift ├── KMPNativeCoroutinesSample.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── iOS App.xcscheme │ │ ├── macOS App.xcscheme │ │ ├── tvOS App.xcscheme │ │ ├── watchOS App WatchKit App (Complication).xcscheme │ │ ├── watchOS App WatchKit App.xcscheme │ │ └── watchOS Tests.xcscheme ├── RxSwift │ ├── ClockRxSwiftViewModel.swift │ ├── RandomLettersRxSwiftViewModel.swift │ ├── RxSwiftObservableIntegrationTests.swift │ └── RxSwiftSingleIntegrationTests.swift ├── UI │ ├── ClockView.swift │ ├── ClockViewModel.swift │ ├── NavigationBarTitle.swift │ ├── RandomLettersView.swift │ ├── RandomLettersViewModel.swift │ └── RootView.swift ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iOS App │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── SceneDelegate.swift ├── macOS App │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── macOS_App.entitlements ├── settings.gradle.kts ├── shared │ ├── build.gradle.kts │ └── src │ │ ├── appleMain │ │ └── kotlin │ │ │ └── com │ │ │ └── rickclephas │ │ │ └── kmp │ │ │ └── nativecoroutines │ │ │ └── sample │ │ │ ├── ClockApple.kt │ │ │ └── issues │ │ │ └── GH206.kt │ │ └── commonMain │ │ └── kotlin │ │ └── com │ │ └── rickclephas │ │ └── kmp │ │ └── nativecoroutines │ │ └── sample │ │ ├── Clock.kt │ │ ├── RandomLettersGenerator.kt │ │ ├── issues │ │ ├── GH206.kt │ │ └── GH219.kt │ │ └── tests │ │ ├── CompilerIntegrationTests.kt │ │ ├── FlowIntegrationTests.kt │ │ ├── IntegrationTests.kt │ │ ├── NewMemoryModelIntegrationTests.kt │ │ ├── SuspendIntegrationTests.kt │ │ └── ThreadLockIntegrationTests.kt ├── tvOS App │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - App Store.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ └── Contents.json │ │ │ └── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── watchOS App WatchKit App │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Interface.storyboard │ └── Info.plist └── watchOS App WatchKit Extension │ ├── Assets.xcassets │ ├── Complication.complicationset │ │ ├── Circular.imageset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Extra Large.imageset │ │ │ └── Contents.json │ │ ├── Graphic Bezel.imageset │ │ │ └── Contents.json │ │ ├── Graphic Circular.imageset │ │ │ └── Contents.json │ │ ├── Graphic Corner.imageset │ │ │ └── Contents.json │ │ ├── Graphic Extra Large.imageset │ │ │ └── Contents.json │ │ ├── Graphic Large Rectangular.imageset │ │ │ └── Contents.json │ │ ├── Modular.imageset │ │ │ └── Contents.json │ │ └── Utilitarian.imageset │ │ │ └── Contents.json │ └── Contents.json │ ├── ComplicationController.swift │ ├── ExtensionDelegate.swift │ ├── HostingController.swift │ ├── Info.plist │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json └── settings.gradle.kts /.github/actions/cache-konan/action.yaml: -------------------------------------------------------------------------------- 1 | name: Cache Konan 2 | description: Caches Konan files 3 | runs: 4 | using: composite 5 | steps: 6 | - uses: actions/cache@v4 7 | with: 8 | path: ~/.konan 9 | key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }} 10 | restore-keys: | 11 | ${{ runner.os }}-konan- 12 | -------------------------------------------------------------------------------- /.github/actions/publish-spm-tag/action.yaml: -------------------------------------------------------------------------------- 1 | name: Publish SPM tag 2 | description: Publishes a SPM tag 3 | inputs: 4 | package-name: 5 | description: The name of the SPM package branch 6 | required: true 7 | version-name: 8 | description: The name of the version tag to publish 9 | required: true 10 | runs: 11 | using: composite 12 | steps: 13 | - shell: bash 14 | run: | 15 | git config --local user.name "GitHub Actions" 16 | git config --local user.email "actions@github.com" 17 | git checkout spm/${{ inputs.package-name }} 18 | git merge --no-edit ${{ inputs.version-name }} 19 | git tag ${{ inputs.version-name }}-spm-${{ inputs.package-name }} 20 | git push origin spm/${{ inputs.package-name }}:spm/${{ inputs.package-name }} 21 | git push origin ${{ inputs.version-name }}-spm-${{ inputs.package-name }} 22 | git checkout - 23 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yaml: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | # pull_request: 5 | # branches: 6 | # - master 7 | jobs: 8 | qodana: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | checks: write 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.event.pull_request.head.sha }} 19 | fetch-depth: 0 20 | - id: get-java-home-11 21 | run: echo "JAVA_HOME_11_X64=$JAVA_HOME_11_X64" >> $GITHUB_OUTPUT 22 | - name: 'Qodana Scan' 23 | uses: JetBrains/qodana-action@v2024.1 24 | with: 25 | args: -v,${{ steps.get-java-home-11.outputs.JAVA_HOME_11_X64 }}:/root/.jdks/jdk11 26 | post-pr-comment: false 27 | env: 28 | QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/release-idea.yaml: -------------------------------------------------------------------------------- 1 | name: Publish a IDEA plugin release 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+-idea-[0-9]+.[0-9]+' 6 | - 'v[0-9]+.[0-9]+.[0-9]+-idea-[0-9]+.[0-9]+-EAP-[0-9]+.[0-9]+' 7 | - 'v[0-9]+.[0-9]+.[0-9]+-ALPHA-[0-9]+-idea-[0-9]+.[0-9]+' 8 | - 'v[0-9]+.[0-9]+.[0-9]+-ALPHA-[0-9]+-idea-[0-9]+.[0-9]+-EAP-[0-9]+.[0-9]+' 9 | env: 10 | JAVA_VERSION: '17' 11 | ORG_GRADLE_PROJECT_buildType: IDE_PLUGIN 12 | jobs: 13 | publish-idea-plugin: 14 | runs-on: macos-14 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Setup JDK 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'zulu' 22 | java-version: ${{ env.JAVA_VERSION }} 23 | - name: Setup Gradle 24 | uses: gradle/actions/setup-gradle@v4 25 | with: 26 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 27 | - name: Publish IDEA plugin 28 | run: ./gradlew publishPlugin 29 | env: 30 | IDEA_CERTIFICATE_CHAIN: ${{ secrets.IDEA_CERTIFICATE_CHAIN }} 31 | IDEA_PRIVATE_KEY: ${{ secrets.IDEA_PRIVATE_KEY }} 32 | IDEA_PRIVATE_KEY_PASSWORD: ${{ secrets.IDEA_PRIVATE_KEY_PASSWORD }} 33 | IDEA_PUBLISH_TOKEN: ${{ secrets.IDEA_PUBLISH_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/run-kotlin-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Kotlin Tests 2 | concurrency: 3 | group: run-kotlin-tests-${{ github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | workflow_dispatch: 7 | workflow_call: 8 | env: 9 | GRADLE_OPTS: -Dorg.gradle.parallel=true -Dorg.gradle.caching=true 10 | jobs: 11 | run-kotlin-tests: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | module: [ annotations, compiler, compiler-embeddable, core, gradle-plugin, ksp ] 16 | include: 17 | - java: '17' 18 | - xcode: '15.2' 19 | - module: compiler 20 | buildType: COMPILER_TESTS 21 | name: ${{ matrix.module }} 22 | runs-on: macos-14 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | - name: Setup Xcode 27 | uses: maxim-lobanov/setup-xcode@v1 28 | with: 29 | xcode-version: ${{ matrix.xcode }} 30 | - name: Setup JDK 31 | uses: actions/setup-java@v4 32 | with: 33 | distribution: zulu 34 | java-version: ${{ matrix.java }} 35 | - name: Setup Gradle 36 | uses: gradle/actions/setup-gradle@v4 37 | with: 38 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 39 | - name: Cache Konan 40 | uses: ./.github/actions/cache-konan 41 | - name: Run tests 42 | env: 43 | ORG_GRADLE_PROJECT_buildType: ${{ matrix.buildType || '' }} 44 | run: ./gradlew :kmp-nativecoroutines-${{ matrix.module }}:check 45 | - name: Archive reports 46 | if: failure() 47 | uses: actions/upload-artifact@v4 48 | with: 49 | name: kmp-nativecoroutines-${{ matrix.module }}-reports 50 | path: '*/build/reports/' 51 | -------------------------------------------------------------------------------- /.github/workflows/run-swift-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Swift Tests 2 | concurrency: 3 | group: run-swift-tests-${{ github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | workflow_dispatch: 7 | workflow_call: 8 | jobs: 9 | run-swift-tests: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | platform: [ iOS, macOS, tvOS, watchOS ] 14 | implementation: [ Async, Combine, RxSwift ] 15 | include: 16 | - xcode: '15.2' 17 | - platform: iOS 18 | destination: platform=iOS Simulator,name=iPhone 16 Pro 19 | - platform: macOS 20 | destination: platform=OS X 21 | - platform: tvOS 22 | destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation) 23 | - platform: watchOS 24 | destination: platform=watchOS Simulator,name=Apple Watch Series 10 (46mm) 25 | name: ${{ format('{0} {1}', matrix.implementation, matrix.platform) }} 26 | runs-on: macos-14 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Setup Xcode 31 | uses: maxim-lobanov/setup-xcode@v1 32 | with: 33 | xcode-version: ${{ matrix.xcode }} 34 | - name: Run tests 35 | run: >- 36 | set -o pipefail && 37 | xcodebuild test 38 | -project KMPNativeCoroutines.xcodeproj 39 | -scheme "KMPNativeCoroutines${{ matrix.implementation }}" 40 | -destination "${{ matrix.destination }}" 41 | | xcbeautify --renderer github-actions 42 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | concurrency: 3 | group: run-tests-${{ github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | types: [opened, synchronize, ready_for_review, reopened] 9 | branches: 10 | - master 11 | - kotlin/** 12 | jobs: 13 | run-kotlin-tests: 14 | if: github.event_name != 'pull_request' || github.event.pull_request.draft == false 15 | name: Kotlin Tests 16 | uses: ./.github/workflows/run-kotlin-tests.yaml 17 | run-swift-tests: 18 | if: github.event_name != 'pull_request' || github.event.pull_request.draft == false 19 | name: Swift Tests 20 | uses: ./.github/workflows/run-swift-tests.yaml 21 | run-integration-tests: 22 | if: github.event_name != 'pull_request' || github.event.pull_request.draft == false 23 | name: Integration Tests 24 | needs: 25 | - run-kotlin-tests 26 | - run-swift-tests 27 | uses: ./.github/workflows/run-integration-tests.yaml 28 | run-idea-tests: 29 | if: github.event_name != 'pull_request' || github.event.pull_request.draft == false 30 | name: IDEA Tests 31 | uses: ./.github/workflows/run-idea-tests.yaml 32 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | kmp-nativecoroutines -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Compiler_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | false 19 | true 20 | false 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Core_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | false 19 | true 20 | false 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/runConfigurations/KSP_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | false 19 | true 20 | false 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_IDE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Verify_Plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | false 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /KMPNativeCoroutines.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KMPNativeCoroutines.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesAsync.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'KMPNativeCoroutinesAsync' 3 | s.version = '1.0.0-ALPHA-44' 4 | s.summary = 'Swift library for Kotlin Coroutines with Swift Async/Await' 5 | 6 | s.homepage = 'https://github.com/rickclephas/KMP-NativeCoroutines' 7 | s.license = 'MIT' 8 | s.authors = 'Rick Clephas' 9 | 10 | s.source = { 11 | :git => 'https://github.com/rickclephas/KMP-NativeCoroutines.git', 12 | :tag => 'v' + s.version.to_s 13 | } 14 | 15 | s.swift_versions = ['5.5'] 16 | s.ios.deployment_target = '13.0' 17 | s.osx.deployment_target = '10.15' 18 | s.watchos.deployment_target = '6.0' 19 | s.tvos.deployment_target = '13.0' 20 | 21 | s.dependency 'KMPNativeCoroutinesCore', s.version.to_s 22 | 23 | s.source_files = 'KMPNativeCoroutinesAsync/**/*.swift' 24 | end 25 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesAsync/AsyncError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncError.swift 3 | // KMPNativeCoroutinesAsync 4 | // 5 | // Created by Rick Clephas on 11/05/2024. 6 | // 7 | 8 | import KMPNativeCoroutinesCore 9 | 10 | /// Awaits the `NativeSuspend` and returns the optional error. 11 | /// - Parameter nativeSuspend: The native suspend function to await. 12 | /// - Returns: The `Error` from the `nativeSuspend`, or `nil`. 13 | public func asyncError( 14 | for nativeSuspend: @escaping NativeSuspend 15 | ) async -> Error? { 16 | do { 17 | try await asyncFunction(for: nativeSuspend) 18 | return nil 19 | } catch { 20 | return error 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesAsync/AsyncResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncResult.swift 3 | // KMPNativeCoroutinesAsync 4 | // 5 | // Created by Rick Clephas on 28/06/2021. 6 | // 7 | 8 | import KMPNativeCoroutinesCore 9 | 10 | /// Awaits the `NativeSuspend` and returns the result. 11 | /// - Parameter nativeSuspend: The native suspend function to await. 12 | /// - Returns: The `Result` from the `nativeSuspend`. 13 | public func asyncResult( 14 | for nativeSuspend: @escaping NativeSuspend 15 | ) async -> Result { 16 | do { 17 | return .success(try await asyncFunction(for: nativeSuspend)) 18 | } catch { 19 | return .failure(error) 20 | } 21 | } 22 | 23 | /// Awaits the `NativeSuspend` and returns the result. 24 | /// - Parameter nativeSuspend: The native suspend function to await. 25 | /// - Returns: The `Result` from the `nativeSuspend`. 26 | public func asyncResult( 27 | for nativeSuspend: @escaping NativeSuspend 28 | ) async -> Result { 29 | do { 30 | return .success(try await asyncFunction(for: nativeSuspend)) 31 | } catch { 32 | return .failure(error) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCombine.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'KMPNativeCoroutinesCombine' 3 | s.version = '1.0.0-ALPHA-44' 4 | s.summary = 'Swift library for Kotlin Coroutines with Combine' 5 | 6 | s.homepage = 'https://github.com/rickclephas/KMP-NativeCoroutines' 7 | s.license = 'MIT' 8 | s.authors = 'Rick Clephas' 9 | 10 | s.source = { 11 | :git => 'https://github.com/rickclephas/KMP-NativeCoroutines.git', 12 | :tag => 'v' + s.version.to_s 13 | } 14 | 15 | s.swift_versions = ['5.0'] 16 | s.ios.deployment_target = '13.0' 17 | s.osx.deployment_target = '10.15' 18 | s.watchos.deployment_target = '6.0' 19 | s.tvos.deployment_target = '13.0' 20 | 21 | s.dependency 'KMPNativeCoroutinesCore', s.version.to_s 22 | s.framework = 'Combine' 23 | 24 | s.source_files = 'KMPNativeCoroutinesCombine/**/*.swift' 25 | end 26 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCombine/FuturePublisher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FuturePublisher.swift 3 | // KMPNativeCoroutinesCombine 4 | // 5 | // Created by Rick Clephas on 28/06/2021. 6 | // 7 | 8 | import Combine 9 | import KMPNativeCoroutinesCore 10 | 11 | /// Creates an `AnyPublisher` for the provided `NativeSuspend` that returns a `NativeFlow`. 12 | /// - Parameter nativeSuspend: The native suspend function to await. 13 | /// - Returns: A publisher that publishes the collected values. 14 | public func createPublisher( 15 | for nativeSuspend: @escaping NativeSuspend, Failure, Unit> 16 | ) -> AnyPublisher { 17 | return createFuture(for: nativeSuspend) 18 | .flatMap { createPublisher(for: $0) } 19 | .eraseToAnyPublisher() 20 | } 21 | 22 | /// Creates an `AnyPublisher` for the provided `NativeSuspend` that returns a `NativeFlow`. 23 | /// - Parameter nativeSuspend: The native suspend function to await. 24 | /// - Returns: A publisher that publishes the collected values. 25 | public func createPublisher( 26 | for nativeSuspend: @escaping NativeSuspend, Failure, Unit> 27 | ) -> AnyPublisher { 28 | return createFuture(for: nativeSuspend) 29 | .flatMap { createPublisher(for: $0) } 30 | .eraseToAnyPublisher() 31 | } 32 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCore.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'KMPNativeCoroutinesCore' 3 | s.version = '1.0.0-ALPHA-44' 4 | s.summary = 'Swift library for Kotlin Coroutines' 5 | 6 | s.homepage = 'https://github.com/rickclephas/KMP-NativeCoroutines' 7 | s.license = 'MIT' 8 | s.authors = 'Rick Clephas' 9 | 10 | s.source = { 11 | :git => 'https://github.com/rickclephas/KMP-NativeCoroutines.git', 12 | :tag => 'v' + s.version.to_s 13 | } 14 | 15 | s.swift_versions = ['5.0'] 16 | s.ios.deployment_target = '9.0' 17 | s.osx.deployment_target = '10.13' 18 | s.watchos.deployment_target = '3.0' 19 | s.tvos.deployment_target = '9.0' 20 | 21 | s.source_files = 'KMPNativeCoroutinesCore/**/*.swift' 22 | end 23 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCore/NativeCallback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeCallback.swift 3 | // KMPNativeCoroutinesCore 4 | // 5 | // Created by Rick Clephas on 06/06/2021. 6 | // 7 | 8 | /// A callback with a single argument. 9 | /// 10 | /// The return value is provided as the second argument. 11 | /// This way Swift doesn't known what it is/how to get it. 12 | public typealias NativeCallback = (T, Unit) -> Unit 13 | 14 | /// A callback with two arguments. 15 | /// 16 | /// The return value is provided as the third argument. 17 | /// This way Swift doesn't known what it is/how to get it. 18 | public typealias NativeCallback2 = (T1, T2, Unit) -> Unit 19 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCore/NativeCancellable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeCancellable.swift 3 | // KMPNativeCoroutinesCore 4 | // 5 | // Created by Rick Clephas on 06/06/2021. 6 | // 7 | 8 | /// A function that cancels the coroutines job. 9 | public typealias NativeCancellable = () -> Unit 10 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCore/NativeFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeFlow.swift 3 | // KMPNativeCoroutinesCore 4 | // 5 | // Created by Rick Clephas on 06/06/2021. 6 | // 7 | 8 | /// A function that collects a Kotlin coroutines Flow via callbacks. 9 | /// 10 | /// The function takes an `onItem`, `onComplete` and `onCancelled` callback 11 | /// and returns a cancellable that can be used to cancel the collection. 12 | public typealias NativeFlow = ( 13 | _ onItem: @escaping NativeCallback2 Unit, Unit>, 14 | _ onComplete: @escaping NativeCallback, 15 | _ onCancelled: @escaping NativeCallback 16 | ) -> NativeCancellable 17 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesCore/NativeSuspend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeSuspend.swift 3 | // KMPNativeCoroutinesCore 4 | // 5 | // Created by Rick Clephas on 06/06/2021. 6 | // 7 | 8 | /// A function that awaits a suspend function via callbacks. 9 | /// 10 | /// The function takes an `onResult`, `onError` and `onCancelled` callback 11 | /// and returns a cancellable that can be used to cancel the suspend function. 12 | public typealias NativeSuspend = ( 13 | _ onResult: @escaping NativeCallback, 14 | _ onError: @escaping NativeCallback, 15 | _ onCancelled: @escaping NativeCallback 16 | ) -> NativeCancellable 17 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesRxSwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'KMPNativeCoroutinesRxSwift' 3 | s.version = '1.0.0-ALPHA-44' 4 | s.summary = 'Swift library for Kotlin Coroutines with RxSwift' 5 | 6 | s.homepage = 'https://github.com/rickclephas/KMP-NativeCoroutines' 7 | s.license = 'MIT' 8 | s.authors = 'Rick Clephas' 9 | 10 | s.source = { 11 | :git => 'https://github.com/rickclephas/KMP-NativeCoroutines.git', 12 | :tag => 'v' + s.version.to_s 13 | } 14 | 15 | s.swift_versions = ['5.0'] 16 | s.ios.deployment_target = '9.0' 17 | s.osx.deployment_target = '10.13' 18 | s.watchos.deployment_target = '3.0' 19 | s.tvos.deployment_target = '9.0' 20 | 21 | s.dependency 'KMPNativeCoroutinesCore', s.version.to_s 22 | s.dependency 'RxSwift', '~> 6.0' 23 | 24 | s.source_files = 'KMPNativeCoroutinesRxSwift/**/*.swift' 25 | end 26 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesRxSwift/Single.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Single.swift 3 | // KMPNativeCoroutinesRxSwift 4 | // 5 | // Created by Rick Clephas on 05/07/2021. 6 | // 7 | 8 | import RxSwift 9 | import KMPNativeCoroutinesCore 10 | 11 | /// Creates a `Single` for the provided `NativeSuspend`. 12 | /// - Parameter nativeSuspend: The native suspend function to await. 13 | /// - Returns: A single that either finishes with a single value or fails with an error. 14 | public func createSingle( 15 | for nativeSuspend: @escaping NativeSuspend 16 | ) -> Single { 17 | return createSingleImpl(for: nativeSuspend) 18 | } 19 | 20 | /// Creates a `Single` for the provided `NativeSuspend`. 21 | /// - Parameter nativeSuspend: The native suspend function to await. 22 | /// - Returns: A single that either finishes with a single value or fails with an error. 23 | public func createSingle( 24 | for nativeSuspend: @escaping NativeSuspend 25 | ) -> Single { 26 | return createSingleImpl(for: nativeSuspend).map { _ in } 27 | } 28 | 29 | private func createSingleImpl( 30 | for nativeSuspend: @escaping NativeSuspend 31 | ) -> Single { 32 | return Single.deferred { 33 | return Single.create { observer in 34 | let nativeCancellable = nativeSuspend({ output, unit in 35 | observer(.success(output)) 36 | return unit 37 | }, { error, unit in 38 | observer(.failure(error)) 39 | return unit 40 | }, { cancellationError, unit in 41 | observer(.failure(cancellationError)) 42 | return unit 43 | }) 44 | return Disposables.create { _ = nativeCancellable() } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /KMPNativeCoroutinesRxSwift/SingleObservable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleObservable.swift 3 | // KMPNativeCoroutinesRxSwift 4 | // 5 | // Created by Rick Clephas on 05/07/2021. 6 | // 7 | 8 | import RxSwift 9 | import KMPNativeCoroutinesCore 10 | 11 | /// Creates an `Observable` for the provided `NativeSuspend` that returns a `NativeFlow`. 12 | /// - Parameter nativeSuspend: The native suspend function to await. 13 | /// - Returns: An observable that publishes the collected values. 14 | public func createObservable( 15 | for nativeSuspend: @escaping NativeSuspend, Failure, Unit> 16 | ) -> Observable { 17 | return createSingle(for: nativeSuspend) 18 | .asObservable() 19 | .flatMap { createObservable(for: $0) } 20 | } 21 | 22 | /// Creates an `Observable` for the provided `NativeSuspend` that returns a `NativeFlow`. 23 | /// - Parameter nativeSuspend: The native suspend function to await. 24 | /// - Returns: An observable that publishes the collected values. 25 | public func createObservable( 26 | for nativeSuspend: @escaping NativeSuspend, Failure, Unit> 27 | ) -> Observable { 28 | return createSingle(for: nativeSuspend) 29 | .asObservable() 30 | .flatMap { createObservable(for: $0) } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rick Clephas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import kotlinx.validation.ExperimentalBCVApi 2 | 3 | plugins { 4 | alias(libs.plugins.kotlinx.binary.compatibility.validator) 5 | } 6 | 7 | buildscript { 8 | repositories { 9 | gradlePluginPortal() 10 | mavenCentral() 11 | } 12 | } 13 | 14 | allprojects { 15 | group = "com.rickclephas.kmp" 16 | version = "1.0.0-ALPHA-44" 17 | } 18 | 19 | apiValidation { 20 | @OptIn(ExperimentalBCVApi::class) 21 | klib { 22 | enabled = true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation(libs.kotlin.gradle.plugin) 7 | implementation(libs.vanniktech.mavenPublish) 8 | } 9 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | dependencyResolutionManagement { 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | versionCatalogs { 9 | create("libs") { 10 | from(files("../gradle/libs.versions.toml")) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/BuildType.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Project 2 | import org.gradle.api.provider.Provider 3 | import org.gradle.kotlin.dsl.registering 4 | 5 | enum class BuildType { 6 | COMPILER_TESTS, IDE_PLUGIN 7 | } 8 | 9 | val Project.buildType: Provider 10 | get() = providers.gradleProperty("buildType") 11 | .filter { it.isNotEmpty() } 12 | .map(BuildType::valueOf) 13 | 14 | fun Project.requireBuildType(buildType: BuildType) = tasks.registering { 15 | val actualBuildType = this@requireBuildType.buildType 16 | doFirst { 17 | if (actualBuildType.orNull != buildType) { 18 | throw IllegalStateException("Build type ${buildType.name} is required. Please run with -PbuildType=${buildType.name}") 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/kmp-nativecoroutines-kotlin-jvm.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/kmp-nativecoroutines-kotlin-multiplatform.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | } 4 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/kmp-nativecoroutines-publish.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import com.vanniktech.maven.publish.SonatypeHost 4 | 5 | plugins { 6 | id("com.vanniktech.maven.publish.base") 7 | } 8 | 9 | mavenPublishing { 10 | publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) 11 | signAllPublications() 12 | configureBasedOnAppliedPlugins() 13 | pom { 14 | name = "KMP-NativeCoroutines" 15 | description = "Swift library for Kotlin Coroutines" 16 | url = "https://github.com/rickclephas/KMP-NativeCoroutines" 17 | licenses { 18 | license { 19 | name = "MIT" 20 | url = "https://opensource.org/licenses/MIT" 21 | } 22 | } 23 | developers { 24 | developer { 25 | id = "rickclephas" 26 | name = "Rick Clephas" 27 | email = "rclephas@gmail.com" 28 | } 29 | } 30 | scm { 31 | url = "https://github.com/rickclephas/KMP-NativeCoroutines" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.configuration-cache=true 3 | org.gradle.jvmargs=-Xmx2g 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickclephas/KMP-NativeCoroutines/26487987d20ef9b5c8972eba72fac01a5200477c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/api/kmp-nativecoroutines-annotations.api: -------------------------------------------------------------------------------- 1 | public abstract interface annotation class com/rickclephas/kmp/nativecoroutines/NativeCoroutineScope : java/lang/annotation/Annotation { 2 | } 3 | 4 | public abstract interface annotation class com/rickclephas/kmp/nativecoroutines/NativeCoroutines : java/lang/annotation/Annotation { 5 | } 6 | 7 | public abstract interface annotation class com/rickclephas/kmp/nativecoroutines/NativeCoroutinesIgnore : java/lang/annotation/Annotation { 8 | } 9 | 10 | public abstract interface annotation class com/rickclephas/kmp/nativecoroutines/NativeCoroutinesRefined : java/lang/annotation/Annotation { 11 | } 12 | 13 | public abstract interface annotation class com/rickclephas/kmp/nativecoroutines/NativeCoroutinesRefinedState : java/lang/annotation/Annotation { 14 | } 15 | 16 | public abstract interface annotation class com/rickclephas/kmp/nativecoroutines/NativeCoroutinesState : java/lang/annotation/Annotation { 17 | } 18 | 19 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | 3 | plugins { 4 | id("kmp-nativecoroutines-kotlin-multiplatform") 5 | id("kmp-nativecoroutines-publish") 6 | } 7 | 8 | kotlin { 9 | explicitApi() 10 | jvmToolchain(11) 11 | 12 | macosX64() 13 | macosArm64() 14 | iosArm64() 15 | iosX64() 16 | iosSimulatorArm64() 17 | watchosArm32() 18 | watchosArm64() 19 | watchosX64() 20 | watchosSimulatorArm64() 21 | watchosDeviceArm64() 22 | tvosArm64() 23 | tvosX64() 24 | tvosSimulatorArm64() 25 | jvm() 26 | js { 27 | browser() 28 | nodejs() 29 | } 30 | linuxArm64() 31 | linuxX64() 32 | mingwX64() 33 | @OptIn(ExperimentalWasmDsl::class) 34 | wasmJs { 35 | browser() 36 | nodejs() 37 | d8() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | /** 4 | * Identifies the `CoroutineScope` property that will be used by 5 | * the autogenerated NativeCoroutines functions and properties. 6 | */ 7 | @Target(AnnotationTarget.PROPERTY) 8 | @Retention(AnnotationRetention.SOURCE) 9 | @MustBeDocumented 10 | public annotation class NativeCoroutineScope 11 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCoroutines.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.experimental.ExperimentalObjCRefinement 4 | import kotlin.native.HidesFromObjC 5 | 6 | /** 7 | * Identifies properties and functions that require a native coroutines version. 8 | */ 9 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) 10 | @Retention(AnnotationRetention.BINARY) 11 | @MustBeDocumented 12 | @OptIn(ExperimentalObjCRefinement::class) 13 | @HidesFromObjC 14 | public annotation class NativeCoroutines 15 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCoroutinesIgnore.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | /** 4 | * Identifies properties and functions that are ignored by the NativeCoroutines compiler. 5 | */ 6 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) 7 | @Retention(AnnotationRetention.SOURCE) 8 | @MustBeDocumented 9 | public annotation class NativeCoroutinesIgnore 10 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCoroutinesRefined.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.experimental.ExperimentalObjCRefinement 4 | import kotlin.native.HidesFromObjC 5 | import kotlin.native.ShouldRefineInSwift 6 | 7 | /** 8 | * Identifies properties and functions that require a native [ShouldRefineInSwift] coroutines version. 9 | */ 10 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) 11 | @Retention(AnnotationRetention.BINARY) 12 | @MustBeDocumented 13 | @OptIn(ExperimentalObjCRefinement::class) 14 | @HidesFromObjC 15 | public annotation class NativeCoroutinesRefined 16 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCoroutinesRefinedState.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.experimental.ExperimentalObjCRefinement 4 | import kotlin.native.HidesFromObjC 5 | import kotlin.native.ShouldRefineInSwift 6 | 7 | /** 8 | * Identifies `StateFlow` properties that require a native [ShouldRefineInSwift] state version. 9 | */ 10 | @Target(AnnotationTarget.PROPERTY) 11 | @Retention(AnnotationRetention.BINARY) 12 | @MustBeDocumented 13 | @OptIn(ExperimentalObjCRefinement::class) 14 | @HidesFromObjC 15 | public annotation class NativeCoroutinesRefinedState 16 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCoroutinesState.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.experimental.ExperimentalObjCRefinement 4 | import kotlin.native.HidesFromObjC 5 | 6 | /** 7 | * Identifies `StateFlow` properties that require a native state version. 8 | */ 9 | @Target(AnnotationTarget.PROPERTY) 10 | @Retention(AnnotationRetention.BINARY) 11 | @MustBeDocumented 12 | @OptIn(ExperimentalObjCRefinement::class) 13 | @HidesFromObjC 14 | public annotation class NativeCoroutinesState 15 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-annotations/src/jvmMain/resources/META-INF/com/rickclephas/kmp/kmp-nativecoroutines-annotations-jvm/verification.properties: -------------------------------------------------------------------------------- 1 | #This is the verification token for the com.rickclephas.kmp:kmp-nativecoroutines-annotations-jvm SDK. 2 | #Sun Jul 14 03:58:30 PDT 2024 3 | token=W3RYNAJ7DVG75HELMMNMMEQFQY 4 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler-embeddable/.gitignore: -------------------------------------------------------------------------------- 1 | src/ -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler-embeddable/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-nativecoroutines-kotlin-jvm") 3 | id("kmp-nativecoroutines-publish") 4 | } 5 | 6 | dependencies { 7 | compileOnly(libs.kotlin.compiler.embeddable) 8 | } 9 | 10 | val syncSources by tasks.registering(Sync::class) { 11 | from(project(":kmp-nativecoroutines-compiler").files("src/main")) 12 | into("src/main") 13 | filter { 14 | when (it) { 15 | "import com.intellij.psi.PsiElement" -> "import org.jetbrains.kotlin.com.intellij.psi.PsiElement" 16 | else -> it 17 | } 18 | } 19 | } 20 | 21 | kotlin { 22 | explicitApi() 23 | jvmToolchain(11) 24 | } 25 | 26 | val sourcesJar by tasks.getting(Jar::class) { 27 | dependsOn(syncSources) 28 | } 29 | 30 | tasks.compileKotlin.configure { 31 | dependsOn(syncSources) 32 | compilerOptions { 33 | freeCompilerArgs.add("-Xjvm-default=all") 34 | } 35 | } 36 | 37 | tasks.processResources.configure { 38 | dependsOn(syncSources) 39 | } 40 | 41 | tasks.clean.configure { 42 | delete("src") 43 | } 44 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.stdlib.default.dependency=false 2 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/KmpNativeCoroutinesCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.config.* 4 | import org.jetbrains.kotlin.compiler.plugin.* 5 | import org.jetbrains.kotlin.config.CompilerConfiguration 6 | 7 | @OptIn(ExperimentalCompilerApi::class) 8 | public class KmpNativeCoroutinesCommandLineProcessor: CommandLineProcessor { 9 | 10 | override val pluginId: String = "com.rickclephas.kmp.nativecoroutines" 11 | override val pluginOptions: Collection = listOf( 12 | EXPOSED_SEVERITY, GENERATED_SOURCE_DIR, K2_MODE, 13 | SUFFIX, FLOW_VALUE_SUFFIX, FLOW_REPLAY_CACHE_SUFFIX, STATE_SUFFIX, STATE_FLOW_SUFFIX 14 | ) 15 | 16 | override fun processOption( 17 | option: AbstractCliOption, 18 | value: String, 19 | configuration: CompilerConfiguration 20 | ): Unit = when (option) { 21 | is ConfigOption<*> -> configuration[option] = value 22 | is ConfigListOption<*> -> configuration.add(option, value) 23 | else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/KmpNativeCoroutinesCompilerPluginRegistrar.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.classic.extensions.KmpNativeCoroutinesStorageComponentContainerContributor 4 | import com.rickclephas.kmp.nativecoroutines.compiler.config.K2_MODE 5 | import com.rickclephas.kmp.nativecoroutines.compiler.config.get 6 | import com.rickclephas.kmp.nativecoroutines.compiler.fir.extensions.KmpNativeCoroutinesFirExtensionRegistrar 7 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.extensions.KmpNativeCoroutinesIrGenerationExtension 8 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 9 | import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar 10 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 11 | import org.jetbrains.kotlin.config.CompilerConfiguration 12 | import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor 13 | import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter 14 | 15 | @OptIn(ExperimentalCompilerApi::class) 16 | public class KmpNativeCoroutinesCompilerPluginRegistrar: CompilerPluginRegistrar() { 17 | 18 | override val supportsK2: Boolean = true 19 | 20 | override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { 21 | FirExtensionRegistrarAdapter.registerExtension(KmpNativeCoroutinesFirExtensionRegistrar(configuration)) 22 | StorageComponentContainerContributor.registerExtension( 23 | KmpNativeCoroutinesStorageComponentContainerContributor(configuration) 24 | ) 25 | if (configuration[K2_MODE]) { 26 | IrGenerationExtension.registerExtension(KmpNativeCoroutinesIrGenerationExtension()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/classic/extensions/KmpNativeCoroutinesStorageComponentContainerContributor.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.classic.extensions 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.config.EXPOSED_SEVERITY 4 | import com.rickclephas.kmp.nativecoroutines.compiler.config.GENERATED_SOURCE_DIR 5 | import com.rickclephas.kmp.nativecoroutines.compiler.config.get 6 | import com.rickclephas.kmp.nativecoroutines.compiler.classic.diagnostics.KmpNativeCoroutinesChecker 7 | import com.rickclephas.kmp.nativecoroutines.compiler.config.K2_MODE 8 | import org.jetbrains.kotlin.config.CompilerConfiguration 9 | import org.jetbrains.kotlin.container.StorageComponentContainer 10 | import org.jetbrains.kotlin.container.useInstance 11 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 12 | import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor 13 | import org.jetbrains.kotlin.platform.TargetPlatform 14 | 15 | internal class KmpNativeCoroutinesStorageComponentContainerContributor( 16 | private val configuration: CompilerConfiguration 17 | ): StorageComponentContainerContributor { 18 | 19 | override fun registerModuleComponents( 20 | container: StorageComponentContainer, 21 | platform: TargetPlatform, 22 | moduleDescriptor: ModuleDescriptor 23 | ) { 24 | val checker = KmpNativeCoroutinesChecker( 25 | exposedSeverity = configuration[EXPOSED_SEVERITY], 26 | generatedSourceDirs = configuration[GENERATED_SOURCE_DIR], 27 | isK2Mode = configuration[K2_MODE], 28 | ) 29 | container.useInstance(checker) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/classic/utils/ImplicitReturnType.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.classic.utils 2 | 3 | import org.jetbrains.kotlin.psi.KtCallableDeclaration 4 | import org.jetbrains.kotlin.psi.KtNamedFunction 5 | 6 | // https://github.com/JetBrains/kotlin/blob/fef7e06fe2603d0a2f53994247a4cbd1467457a5/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExplicitApiDeclarationChecker.kt#L125-L134 7 | internal fun KtCallableDeclaration.hasImplicitReturnType(): Boolean { 8 | if (typeReference != null) return false 9 | if (this is KtNamedFunction && hasBlockBody()) return false 10 | return true 11 | } 12 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/classic/utils/NativeCoroutinesAnnotations.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.classic.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.NativeCoroutinesAnnotation 4 | import org.jetbrains.kotlin.descriptors.annotations.Annotated 5 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 6 | 7 | internal fun Annotated.getNativeCoroutinesAnnotations(): Map = buildMap { 8 | for (annotation in annotations) { 9 | val fqName = annotation.fqName ?: continue 10 | val nativeCoroutinesAnnotation = NativeCoroutinesAnnotation.forFqName(fqName) ?: continue 11 | put(nativeCoroutinesAnnotation, annotation) 12 | } 13 | } 14 | 15 | internal val AnnotationDescriptor.isNativeCoroutinesAnnotation: Boolean get() { 16 | val fqName = fqName ?: return false 17 | return NativeCoroutinesAnnotation.entries.any { it.fqName == fqName } 18 | } 19 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/classic/utils/ObjCRefinement.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.classic.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.FqNames 4 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 5 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 6 | import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass 7 | 8 | internal val DeclarationDescriptor.isRefined: Boolean 9 | get() = annotations.any { annotation -> 10 | !annotation.isNativeCoroutinesAnnotation && annotation.isRefinementAnnotation 11 | } 12 | 13 | private val AnnotationDescriptor.isRefinementAnnotation: Boolean 14 | get() = annotationClass?.annotations?.any { metaAnnotation -> 15 | val fqName = metaAnnotation.fqName 16 | fqName == FqNames.hidesFromObjC || fqName == FqNames.refinesInSwift 17 | } ?: false 18 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/config/CompilerConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.config 2 | 3 | import org.jetbrains.kotlin.config.CompilerConfiguration 4 | 5 | internal operator fun CompilerConfiguration.set(option: ConfigOption, value: String) = 6 | put(option.configKey, option.parse(value)) 7 | 8 | internal operator fun CompilerConfiguration.get(option: ConfigOption): T? = 9 | get(option.configKey) 10 | 11 | internal operator fun CompilerConfiguration.get(option: ConfigOptionWithDefault): T = 12 | get(option as ConfigOption) ?: option.defaultValue 13 | 14 | internal fun CompilerConfiguration.add(option: ConfigListOption, value: String) = 15 | add(option.configKey, option.parse(value)) 16 | 17 | internal operator fun CompilerConfiguration.get(option: ConfigListOption): List = 18 | getList(option.configKey) 19 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/config/ConfigListOption.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.config 2 | 3 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 4 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 5 | 6 | public abstract class ConfigListOption(final override val optionName: String): AbstractCliOption { 7 | override val required: Boolean = false 8 | final override val allowMultipleOccurrences: Boolean = true 9 | public val configKey: CompilerConfigurationKey> = CompilerConfigurationKey>(optionName) 10 | public abstract fun parse(value: String): T 11 | } 12 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/config/ConfigOption.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.config 2 | 3 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 4 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 5 | 6 | public abstract class ConfigOption(final override val optionName: String): AbstractCliOption { 7 | override val required: Boolean = true 8 | final override val allowMultipleOccurrences: Boolean = false 9 | public val configKey: CompilerConfigurationKey = CompilerConfigurationKey(optionName) 10 | public abstract fun parse(value: String): T 11 | } 12 | 13 | public abstract class ConfigOptionWithDefault(optionName: String): ConfigOption(optionName) { 14 | public abstract val defaultValue: T 15 | } 16 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/config/ExposedSeverity.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.config 2 | 3 | public enum class ExposedSeverity { 4 | NONE, WARNING, ERROR 5 | } 6 | 7 | public val EXPOSED_SEVERITY: ConfigOptionWithDefault = 8 | object : ConfigOptionWithDefault("exposedSeverity") { 9 | override val description: String = "Specifies the severity of the exposed coroutines check" 10 | override val valueDescription: String = "NONE/WARNING/ERROR" 11 | override val defaultValue: ExposedSeverity = ExposedSeverity.WARNING 12 | override fun parse(value: String): ExposedSeverity = ExposedSeverity.valueOf(value) 13 | } 14 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/config/GeneratedSourceDir.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.config 2 | 3 | import java.nio.file.Path 4 | import kotlin.io.path.Path 5 | 6 | public val GENERATED_SOURCE_DIR: ConfigListOption = 7 | object : ConfigListOption("generatedSourceDir") { 8 | override val description: String = "Specifies a directory containing generated sources" 9 | override val valueDescription: String = "Directory path" 10 | override fun parse(value: String): Path = Path(value) 11 | } 12 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/config/K2Mode.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.config 2 | 3 | public val K2_MODE: ConfigOptionWithDefault = 4 | object : ConfigOptionWithDefault("k2Mode") { 5 | override val required: Boolean = false 6 | override val description: String = "Indicates if the plugin should be run in K2 mode" 7 | override val valueDescription: String = "true/false" 8 | override val defaultValue: Boolean = false 9 | override fun parse(value: String): Boolean = value.toBooleanStrict() 10 | } 11 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/codegen/TodoCall.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.fir.utils.asFirExpression 4 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CallableIds 5 | import org.jetbrains.kotlin.fir.FirSession 6 | import org.jetbrains.kotlin.fir.expressions.FirFunctionCall 7 | import org.jetbrains.kotlin.fir.expressions.buildResolvedArgumentList 8 | import org.jetbrains.kotlin.fir.expressions.builder.buildFunctionCall 9 | import org.jetbrains.kotlin.fir.references.builder.buildResolvedNamedReference 10 | import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider 11 | import org.jetbrains.kotlin.fir.symbols.SymbolInternals 12 | import org.jetbrains.kotlin.fir.types.constructClassLikeType 13 | import org.jetbrains.kotlin.name.StandardClassIds 14 | 15 | internal fun FirSession.buildTodoCall(reason: String): FirFunctionCall = buildFunctionCall { 16 | val callableId = CallableIds.todo 17 | val todoSymbol = symbolProvider.getTopLevelFunctionSymbols(callableId.packageName, callableId.callableName) 18 | .first { it.valueParameterSymbols.size == 1 } // we have 2 symbols in IDE (K/N and Gradle stdlib) 19 | coneTypeOrNull = StandardClassIds.Nothing.constructClassLikeType() 20 | @OptIn(SymbolInternals::class) 21 | val reasonParameter = todoSymbol.valueParameterSymbols.first().fir 22 | argumentList = buildResolvedArgumentList(null, linkedMapOf(reason.asFirExpression() to reasonParameter)) 23 | calleeReference = buildResolvedNamedReference { 24 | name = todoSymbol.name 25 | resolvedSymbol = todoSymbol 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/extensions/KmpNativeCoroutinesFirAdditionalCheckersExtension.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.extensions 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.config.EXPOSED_SEVERITY 4 | import com.rickclephas.kmp.nativecoroutines.compiler.config.GENERATED_SOURCE_DIR 5 | import com.rickclephas.kmp.nativecoroutines.compiler.config.K2_MODE 6 | import com.rickclephas.kmp.nativecoroutines.compiler.config.get 7 | import com.rickclephas.kmp.nativecoroutines.compiler.fir.diagnostics.FirKmpNativeCoroutinesDeclarationChecker 8 | import org.jetbrains.kotlin.config.CompilerConfiguration 9 | import org.jetbrains.kotlin.fir.FirSession 10 | import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers 11 | import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension 12 | 13 | internal class KmpNativeCoroutinesFirAdditionalCheckersExtension( 14 | session: FirSession, 15 | configuration: CompilerConfiguration 16 | ): FirAdditionalCheckersExtension(session) { 17 | 18 | override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() { 19 | override val callableDeclarationCheckers = setOf( 20 | FirKmpNativeCoroutinesDeclarationChecker( 21 | exposedSeverity = configuration[EXPOSED_SEVERITY], 22 | generatedSourceDirs = configuration[GENERATED_SOURCE_DIR], 23 | isK2Mode = configuration[K2_MODE], 24 | ) 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/extensions/KmpNativeCoroutinesFirExtensionRegistrar.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.extensions 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.config.K2_MODE 4 | import com.rickclephas.kmp.nativecoroutines.compiler.config.get 5 | import org.jetbrains.kotlin.config.CompilerConfiguration 6 | import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar 7 | 8 | internal class KmpNativeCoroutinesFirExtensionRegistrar( 9 | private val configuration: CompilerConfiguration 10 | ): FirExtensionRegistrar() { 11 | override fun ExtensionRegistrarContext.configurePlugin() { 12 | +::KmpNativeCoroutinesFirAdditionalCheckersExtension.bind(configuration) 13 | if (configuration[K2_MODE]) { 14 | +::KmpNativeCoroutinesDeclarationGenerationExtension.bind(configuration) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/CoroutinesReturnType.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.ClassIds 4 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CoroutinesReturnType 5 | import org.jetbrains.kotlin.fir.FirSession 6 | import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol 7 | import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration 8 | import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass 9 | import org.jetbrains.kotlin.fir.resolve.isSubclassOf 10 | import org.jetbrains.kotlin.fir.types.toLookupTag 11 | 12 | internal fun FirCallableDeclaration.getCoroutinesReturnType(session: FirSession): CoroutinesReturnType? { 13 | val symbol = returnTypeRef.toClassLikeSymbol(session)?.fullyExpandedClass(session) ?: return null 14 | return coroutinesReturnTypes.firstNotNullOfOrNull { (lookupTag, returnType) -> 15 | returnType.takeIf { symbol.isSubclassOf(lookupTag, session, isStrict = false, lookupInterfaces = true) } 16 | } 17 | } 18 | 19 | private val coroutinesReturnTypes = mapOf( 20 | ClassIds.stateFlow.toLookupTag() to CoroutinesReturnType.Flow.State, 21 | ClassIds.flow.toLookupTag() to CoroutinesReturnType.Flow.Generic, 22 | ClassIds.coroutineScope.toLookupTag() to CoroutinesReturnType.CoroutineScope, 23 | ) 24 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/FirDeclarationStatus.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import org.jetbrains.kotlin.descriptors.Modality 4 | import org.jetbrains.kotlin.descriptors.isInterface 5 | import org.jetbrains.kotlin.fir.FirSession 6 | import org.jetbrains.kotlin.fir.analysis.checkers.classKind 7 | import org.jetbrains.kotlin.fir.containingClassLookupTag 8 | import org.jetbrains.kotlin.fir.declarations.FirDeclarationStatus 9 | import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl 10 | import org.jetbrains.kotlin.fir.resolve.toSymbol 11 | import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol 12 | import org.jetbrains.kotlin.fir.toEffectiveVisibility 13 | 14 | internal fun FirCallableSymbol<*>.getGeneratedDeclarationStatus(session: FirSession): FirDeclarationStatus? { 15 | val visibility = rawStatus.visibility 16 | if (!visibility.isPublicAPI) return null 17 | val ownerSymbol = containingClassLookupTag()?.toSymbol(session) 18 | val modality = when (ownerSymbol?.classKind?.isInterface) { 19 | true -> Modality.OPEN 20 | else -> Modality.FINAL 21 | } 22 | val effectiveVisibility = visibility.toEffectiveVisibility(ownerSymbol) 23 | return FirResolvedDeclarationStatusImpl(visibility, modality, effectiveVisibility) 24 | } 25 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/FirLiteralExpression.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import org.jetbrains.kotlin.fir.expressions.FirLiteralExpression 4 | import org.jetbrains.kotlin.fir.expressions.builder.buildLiteralExpression 5 | import org.jetbrains.kotlin.types.ConstantValueKind 6 | 7 | internal fun String.asFirExpression(): FirLiteralExpression = 8 | buildLiteralExpression(null, ConstantValueKind.String, this, setType = true) 9 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/FirResolvedTypeRef.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import org.jetbrains.kotlin.fir.symbols.SymbolInternals 4 | import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol 5 | import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef 6 | import org.jetbrains.kotlin.fir.types.FirUserTypeRef 7 | 8 | /** 9 | * Safely returns the [resolvedReturnTypeRef][FirCallableSymbol.resolvedReturnTypeRef]. 10 | * Note this returns `null` if the reference can't be resolved. 11 | */ 12 | @OptIn(SymbolInternals::class) 13 | internal val FirCallableSymbol<*>.resolvedReturnTypeRefOrNull: FirResolvedTypeRef? 14 | get() = when (fir.returnTypeRef) { 15 | is FirResolvedTypeRef, is FirUserTypeRef -> resolvedReturnTypeRef 16 | else -> null 17 | } 18 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/ImplicitReturnType.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import org.jetbrains.kotlin.KtRealSourceElementKind 4 | import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration 5 | import org.jetbrains.kotlin.fir.declarations.FirFunction 6 | import org.jetbrains.kotlin.fir.declarations.FirProperty 7 | import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor 8 | import org.jetbrains.kotlin.fir.expressions.impl.FirSingleExpressionBlock 9 | 10 | // https://github.com/JetBrains/kotlin/blob/da6501a98a8f85dd3e7d94dcc17e050b3fefaaa4/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirExplicitApiDeclarationChecker.kt#L150C9-L159 11 | internal fun FirCallableDeclaration.hasImplicitReturnType(): Boolean { 12 | // It's an explicit type, the check always should be skipped 13 | if (returnTypeRef.source?.kind == KtRealSourceElementKind) return false 14 | 15 | return this is FirProperty || 16 | this is FirFunction && 17 | // It's allowed to have implicit return type for getters, for setters the return type is always `Unit`. 18 | // The return type of the outer property is only worth considering. 19 | this !is FirPropertyAccessor && 20 | // Implicit return type can exist only for single-expression functions, unspecified type for regular functions is incorrect. 21 | body is FirSingleExpressionBlock 22 | } 23 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/NativeConeKotlinType.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CallableSignature 4 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.ClassIds 5 | import org.jetbrains.kotlin.fir.types.ConeKotlinType 6 | import org.jetbrains.kotlin.fir.types.ConeTypeProjection 7 | import org.jetbrains.kotlin.fir.types.constructClassLikeType 8 | 9 | internal fun FirCallableSignature.getNativeType( 10 | type: CallableSignature.Type, 11 | isSuspend: Boolean = false 12 | ): ConeKotlinType { 13 | var nativeType = getRawType(type) 14 | if (type is CallableSignature.Type.Flow) { 15 | val typeArgs = arrayOf(getRawType(type.valueType)) 16 | nativeType = ClassIds.nativeFlow.constructClassLikeType(typeArgs, type.isNullable) 17 | } 18 | if (isSuspend) { 19 | val typeArgs = arrayOf(nativeType) 20 | nativeType = ClassIds.nativeSuspend.constructClassLikeType(typeArgs) 21 | } 22 | return nativeType 23 | } 24 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/NativeCoroutinesAnnotations.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.NativeCoroutinesAnnotation 4 | import org.jetbrains.kotlin.fir.FirAnnotationContainer 5 | import org.jetbrains.kotlin.fir.FirSession 6 | import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId 7 | import org.jetbrains.kotlin.fir.expressions.FirAnnotation 8 | import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol 9 | 10 | internal fun FirAnnotationContainer.getNativeCoroutinesAnnotations( 11 | session: FirSession 12 | ): Map = annotations.getNativeCoroutinesAnnotations(session) 13 | 14 | internal fun FirBasedSymbol<*>.getNativeCoroutinesAnnotations( 15 | session: FirSession 16 | ): Map = resolvedAnnotationsWithClassIds.getNativeCoroutinesAnnotations(session) 17 | 18 | private fun List.getNativeCoroutinesAnnotations( 19 | session: FirSession 20 | ): Map = buildMap { 21 | for (annotation in this@getNativeCoroutinesAnnotations) { 22 | val classId = annotation.toAnnotationClassId(session) ?: continue 23 | val nativeCoroutinesAnnotation = NativeCoroutinesAnnotation.forClassId(classId) ?: continue 24 | put(nativeCoroutinesAnnotation, annotation) 25 | } 26 | } 27 | 28 | internal fun FirAnnotation.isNativeCoroutinesAnnotation(session: FirSession): Boolean { 29 | val classId = toAnnotationClassId(session) ?: return false 30 | return NativeCoroutinesAnnotation.entries.any { it.classId == classId } 31 | } 32 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/NativeCoroutinesDeclarationKey.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CallableSignature 4 | import org.jetbrains.kotlin.GeneratedDeclarationKey 5 | 6 | internal data class NativeCoroutinesDeclarationKey( 7 | val type: Type, 8 | val callableSignature: CallableSignature 9 | ): GeneratedDeclarationKey() { 10 | enum class Type { 11 | NATIVE, STATE_FLOW_VALUE, SHARED_FLOW_REPLAY_CACHE 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/fir/utils/ObjCRefinement.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.fir.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.ClassIds 4 | import org.jetbrains.kotlin.fir.FirSession 5 | import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration 6 | import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId 7 | import org.jetbrains.kotlin.fir.declarations.toAnnotationClassLikeSymbol 8 | import org.jetbrains.kotlin.fir.expressions.FirAnnotation 9 | 10 | internal fun FirCallableDeclaration.isRefined(session: FirSession): Boolean = 11 | annotations.any { annotation -> 12 | !annotation.isNativeCoroutinesAnnotation(session) && annotation.isRefinementAnnotation(session) 13 | } 14 | 15 | private fun FirAnnotation.isRefinementAnnotation(session: FirSession): Boolean = 16 | toAnnotationClassLikeSymbol(session)?.resolvedAnnotationsWithClassIds.orEmpty().any { metaAnnotation -> 17 | val classId = metaAnnotation.toAnnotationClassId(session) 18 | classId == ClassIds.hidesFromObjC || classId == ClassIds.refinesInSwift 19 | } 20 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/GeneratorContext.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CallableIds 4 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.ClassIds 5 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 6 | import org.jetbrains.kotlin.ir.builders.IrGeneratorContext 7 | 8 | internal class GeneratorContext( 9 | pluginContext: IrPluginContext 10 | ): IrGeneratorContext by pluginContext { 11 | 12 | val coroutineScopeSymbol = pluginContext.referenceClass(ClassIds.coroutineScope)!! 13 | 14 | val asNativeFlowSymbol = pluginContext.referenceFunctions(CallableIds.asNativeFlow).single() 15 | val nativeSuspendSymbol = pluginContext.referenceFunctions(CallableIds.nativeSuspend).single() 16 | 17 | val sharedFlowReplayCacheSymbol = pluginContext.referenceProperties(CallableIds.sharedFlowReplayCache).single() 18 | val stateFlowValueSymbol = pluginContext.referenceProperties(CallableIds.stateFlowValue).single() 19 | val mutableStateFlowValueSymbol = pluginContext.referenceProperties(CallableIds.mutableStateFlowValue).single() 20 | 21 | val observableViewModelSymbol = pluginContext.referenceClass(ClassIds.observableViewModel) 22 | val observableViewModelScopeSymbol = pluginContext.referenceProperties(CallableIds.observableViewModelScope).singleOrNull() 23 | val observableCoroutineScopeSymbol = pluginContext.referenceProperties(CallableIds.observableCoroutineScope).singleOrNull() 24 | val androidxViewModelSymbol = pluginContext.referenceClass(ClassIds.androidxViewModel) 25 | val androidxViewModelScopeSymbol = pluginContext.referenceProperties(CallableIds.androidxViewModelScope).singleOrNull() 26 | } 27 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/Lambda.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder 4 | import org.jetbrains.kotlin.descriptors.DescriptorVisibilities 5 | import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder 6 | import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope 7 | import org.jetbrains.kotlin.ir.builders.declarations.buildFun 8 | import org.jetbrains.kotlin.ir.builders.irBlockBody 9 | import org.jetbrains.kotlin.ir.builders.parent 10 | import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin 11 | import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression 12 | import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin 13 | import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl 14 | import org.jetbrains.kotlin.ir.types.IrType 15 | import org.jetbrains.kotlin.name.Name 16 | 17 | internal fun IrBuilderWithScope.irLambda( 18 | isSuspend: Boolean, 19 | returnType: IrType, 20 | lambdaType: IrType, 21 | body: IrBlockBodyBuilder.() -> Unit 22 | ): IrFunctionExpression { 23 | val function = context.irFactory.buildFun { 24 | startOffset = this@irLambda.startOffset 25 | endOffset = this@irLambda.endOffset 26 | name = Name.special("") 27 | visibility = DescriptorVisibilities.LOCAL 28 | origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA 29 | this.isSuspend = isSuspend 30 | this.returnType = returnType 31 | }.apply { 32 | parent = this@irLambda.parent 33 | this.body = DeclarationIrBuilder(context, symbol).irBlockBody(body = body) 34 | } 35 | return IrFunctionExpressionImpl( 36 | startOffset, 37 | endOffset, 38 | lambdaType, 39 | function, 40 | IrStatementOrigin.LAMBDA 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/NativeFunction.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.utils.IrBlockBodyExpression.Companion.irGet 4 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CallableSignature 5 | import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder 6 | import org.jetbrains.kotlin.ir.builders.* 7 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 8 | import org.jetbrains.kotlin.ir.expressions.IrBlockBody 9 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 10 | 11 | @UnsafeDuringIrConstructionAPI 12 | internal fun GeneratorContext.buildNativeFunctionBody( 13 | function: IrSimpleFunction, 14 | originalFunction: IrSimpleFunction, 15 | callableSignature: CallableSignature 16 | ): IrBlockBody = DeclarationIrBuilder( 17 | generatorContext = this, 18 | symbol = function.symbol, 19 | ).irBlockBody { 20 | val coroutineScope = irTemporary(irCallCoroutineScope(originalFunction, function)) 21 | var expression = irCallOriginalFunction(originalFunction, function) 22 | if (callableSignature.returnType is CallableSignature.Type.Flow) { 23 | expression = irCallAsNativeFlow(expression, coroutineScope) 24 | } 25 | if (callableSignature.isSuspend) { 26 | expression = irCallNativeSuspend(expression, coroutineScope) 27 | } 28 | +irReturn(irGet(expression)) 29 | } 30 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/NativeProperty.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.utils.IrBlockBodyExpression.Companion.irGet 4 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.CallableSignature 5 | import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder 6 | import org.jetbrains.kotlin.ir.builders.* 7 | import org.jetbrains.kotlin.ir.declarations.IrProperty 8 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 9 | import org.jetbrains.kotlin.ir.expressions.IrBlockBody 10 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 11 | 12 | @UnsafeDuringIrConstructionAPI 13 | internal fun GeneratorContext.buildNativePropertyGetterBody( 14 | function: IrSimpleFunction, 15 | originalProperty: IrProperty, 16 | callableSignature: CallableSignature 17 | ): IrBlockBody = DeclarationIrBuilder( 18 | generatorContext = this, 19 | symbol = function.symbol, 20 | ).irBlockBody { 21 | val originalGetter = originalProperty.getter 22 | require(originalGetter != null) 23 | val coroutineScope = irTemporary(irCallCoroutineScope(originalGetter, function)) 24 | var expression = irCallOriginalPropertyGetter(originalGetter, function) 25 | if (callableSignature.returnType is CallableSignature.Type.Flow) { 26 | expression = irCallAsNativeFlow(expression, coroutineScope) 27 | } 28 | +irReturn(irGet(expression)) 29 | } 30 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/NativeSuspend.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.utils.IrBlockBodyExpression 4 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.utils.IrBlockBodyExpression.Companion.irGet 5 | import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope 6 | import org.jetbrains.kotlin.ir.builders.irCall 7 | import org.jetbrains.kotlin.ir.builders.irGet 8 | import org.jetbrains.kotlin.ir.builders.irReturn 9 | import org.jetbrains.kotlin.ir.declarations.IrVariable 10 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 11 | import org.jetbrains.kotlin.ir.types.IrSimpleType 12 | import org.jetbrains.kotlin.ir.util.substitute 13 | 14 | @UnsafeDuringIrConstructionAPI 15 | internal fun IrBuilderWithScope.irCallNativeSuspend( 16 | blockExpression: IrBlockBodyExpression, 17 | coroutineScope: IrVariable, 18 | ): IrBlockBodyExpression { 19 | val context = context as GeneratorContext 20 | val expressionType = blockExpression.type as IrSimpleType 21 | val lambdaType = context.nativeSuspendSymbol.owner.run { 22 | valueParameters[1].type.substitute(typeParameters, listOf(expressionType)) 23 | } 24 | val returnType = context.nativeSuspendSymbol.owner.run { 25 | returnType.substitute(typeParameters, listOf(expressionType)) 26 | } 27 | return IrBlockBodyExpression(returnType) { 28 | val lambda = irLambda(true, expressionType, lambdaType) { 29 | +irReturn(irGet(blockExpression)) 30 | } 31 | irCall(context.nativeSuspendSymbol, returnType).apply { 32 | putTypeArgument(0, expressionType) 33 | putValueArgument(0, irGet(coroutineScope)) 34 | putValueArgument(1, lambda) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/OriginalFunction.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.utils.IrBlockBodyExpression 4 | import org.jetbrains.kotlin.ir.builders.irCall 5 | import org.jetbrains.kotlin.ir.builders.irGet 6 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 7 | import org.jetbrains.kotlin.ir.util.passTypeArgumentsFrom 8 | 9 | internal fun irCallOriginalFunction( 10 | originalFunction: IrSimpleFunction, 11 | function: IrSimpleFunction 12 | ) = IrBlockBodyExpression(originalFunction.returnType) { 13 | irCall(originalFunction).apply { 14 | dispatchReceiver = function.dispatchReceiverParameter?.let { irGet(it) } 15 | extensionReceiver = function.extensionReceiverParameter?.let { irGet(it) } 16 | passTypeArgumentsFrom(function) 17 | function.valueParameters.forEachIndexed { index, parameter -> 18 | putValueArgument(index, irGet(parameter)) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/codegen/OriginalProperty.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.codegen 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.ir.utils.IrBlockBodyExpression 4 | import org.jetbrains.kotlin.ir.builders.irCall 5 | import org.jetbrains.kotlin.ir.builders.irGet 6 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 7 | import org.jetbrains.kotlin.ir.util.passTypeArgumentsFrom 8 | 9 | internal fun irCallOriginalPropertyGetter( 10 | originalGetter: IrSimpleFunction, 11 | propertyFunction: IrSimpleFunction 12 | ): IrBlockBodyExpression = IrBlockBodyExpression(originalGetter.returnType) { 13 | irCall(originalGetter).apply { 14 | dispatchReceiver = propertyFunction.dispatchReceiverParameter?.let { irGet(it) } 15 | extensionReceiver = propertyFunction.extensionReceiverParameter?.let { irGet(it) } 16 | passTypeArgumentsFrom(propertyFunction) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/extensions/KmpNativeCoroutinesIrGenerationExtension.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.extensions 2 | 3 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 4 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 5 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 6 | import org.jetbrains.kotlin.ir.visitors.acceptVoid 7 | 8 | internal class KmpNativeCoroutinesIrGenerationExtension: IrGenerationExtension { 9 | override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { 10 | moduleFragment.acceptVoid(KmpNativeCoroutinesIrTransformer(pluginContext)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/utils/FlowValueTypeArg.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.utils 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.utils.FqNames 4 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 5 | import org.jetbrains.kotlin.ir.types.* 6 | import org.jetbrains.kotlin.ir.util.getAllSubstitutedSupertypes 7 | import org.jetbrains.kotlin.ir.util.hasEqualFqName 8 | import org.jetbrains.kotlin.ir.util.substitute 9 | 10 | private val flowFqNames = listOf(FqNames.flow, FqNames.stateFlow) 11 | 12 | @UnsafeDuringIrConstructionAPI 13 | internal fun IrType.getFlowValueTypeArg(): IrTypeArgument { 14 | val irClass = classOrFail.owner 15 | this as IrSimpleType // smartcast not provided by classOrFail 16 | if (flowFqNames.any(irClass::hasEqualFqName)) { 17 | return arguments.first() 18 | } 19 | var flowType = getAllSubstitutedSupertypes(irClass).firstOrNull { 20 | flowFqNames.any(it.classOrFail.owner::hasEqualFqName) 21 | } ?: error("$this is not a Flow type") 22 | val arguments = arguments.map { it.typeOrFail } 23 | flowType = flowType.substitute(irClass.typeParameters, arguments) as IrSimpleType 24 | return flowType.arguments.first() 25 | } 26 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/ir/utils/IrBlockBodyExpression.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.ir.utils 2 | 3 | import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder 4 | import org.jetbrains.kotlin.ir.expressions.IrExpression 5 | import org.jetbrains.kotlin.ir.types.IrType 6 | 7 | internal class IrBlockBodyExpression( 8 | val type: IrType, 9 | private val build: IrBlockBodyBuilder.() -> IrExpression 10 | ) { 11 | companion object { 12 | fun IrBlockBodyBuilder.irGet(expression: IrBlockBodyExpression): IrExpression = expression.build(this) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/CallableIds.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.CallableId 4 | import org.jetbrains.kotlin.name.FqName 5 | import org.jetbrains.kotlin.name.Name 6 | 7 | internal object CallableIds { 8 | val todo = CallableId(FqNames.kotlin, Name.identifier("TODO")) 9 | 10 | val asNativeFlow = CallableId(FqNames.nativeCoroutines, Name.identifier("asNativeFlow")) 11 | val nativeSuspend = CallableId(FqNames.nativeCoroutines, Name.identifier("nativeSuspend")) 12 | 13 | val sharedFlowReplayCache = CallableId(ClassIds.sharedFlow, Name.identifier("replayCache")) 14 | val stateFlowValue = CallableId(ClassIds.stateFlow, Name.identifier("value")) 15 | val mutableStateFlowValue = CallableId(ClassIds.mutableStateFlow, Name.identifier("value")) 16 | 17 | val observableViewModelScope = CallableId(ClassIds.observableViewModel, Name.identifier("viewModelScope")) 18 | val observableCoroutineScope = CallableId(FqName("com.rickclephas.kmp.observableviewmodel"), Name.identifier("coroutineScope")) 19 | val androidxViewModelScope = CallableId(FqName("androidx.lifecycle"), Name.identifier("viewModelScope")) 20 | } 21 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/CallableSignature.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.Name 4 | 5 | internal data class CallableSignature( 6 | val isSuspend: Boolean, 7 | val valueParameters: List>, 8 | val returnType: Type 9 | ) { 10 | sealed class Type { 11 | abstract val rawTypeIndex: Int 12 | abstract val isNullable: Boolean 13 | 14 | sealed class Flow: Type() { 15 | abstract val valueType: Type 16 | 17 | data class Simple( 18 | override val rawTypeIndex: Int, 19 | override val isNullable: Boolean, 20 | override val valueType: Type 21 | ): Flow() 22 | data class Shared( 23 | override val rawTypeIndex: Int, 24 | override val isNullable: Boolean, 25 | override val valueType: Type 26 | ): Flow() 27 | data class State( 28 | override val rawTypeIndex: Int, 29 | override val isNullable: Boolean, 30 | override val valueType: Type, 31 | val isMutable: Boolean 32 | ): Flow() 33 | } 34 | 35 | data class Raw( 36 | override val rawTypeIndex: Int, 37 | override val isNullable: Boolean 38 | ): Type() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/ClassIds.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.ClassId 4 | 5 | internal object ClassIds { 6 | val deprecated = ClassId.topLevel(FqNames.deprecated) 7 | 8 | val hidesFromObjC = ClassId.topLevel(FqNames.hidesFromObjC) 9 | val objCName = ClassId.topLevel(FqNames.objCName) 10 | val refinesInSwift = ClassId.topLevel(FqNames.refinesInSwift) 11 | val shouldRefineInSwift = ClassId.topLevel(FqNames.shouldRefineInSwift) 12 | 13 | val coroutineScope = ClassId.topLevel(FqNames.coroutineScope) 14 | val flow = ClassId.topLevel(FqNames.flow) 15 | val sharedFlow = ClassId.topLevel(FqNames.sharedFlow) 16 | val stateFlow = ClassId.topLevel(FqNames.stateFlow) 17 | val mutableStateFlow = ClassId.topLevel(FqNames.mutableStateFlow) 18 | 19 | val nativeFlow = ClassId.topLevel(FqNames.nativeFlow) 20 | val nativeSuspend = ClassId.topLevel(FqNames.nativeSuspend) 21 | 22 | val observableViewModel = ClassId.topLevel(FqNames.observableViewModel) 23 | val androidxViewModel = ClassId.topLevel(FqNames.androidxViewModel) 24 | } 25 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/CoroutinesReturnType.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | internal sealed class CoroutinesReturnType private constructor() { 4 | data object CoroutineScope: CoroutinesReturnType() 5 | sealed class Flow private constructor(): CoroutinesReturnType() { 6 | data object Generic: Flow() 7 | data object State: Flow() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/FqNames.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.FqName 4 | 5 | internal object FqNames { 6 | val kotlin = FqName("kotlin") 7 | 8 | val deprecated = FqName("kotlin.Deprecated") 9 | 10 | val hidesFromObjC = FqName("kotlin.native.HidesFromObjC") 11 | val objCName = FqName("kotlin.native.ObjCName") 12 | val refinesInSwift = FqName("kotlin.native.RefinesInSwift") 13 | val shouldRefineInSwift = FqName("kotlin.native.ShouldRefineInSwift") 14 | 15 | val coroutineScope = FqName("kotlinx.coroutines.CoroutineScope") 16 | val flow = FqName("kotlinx.coroutines.flow.Flow") 17 | val sharedFlow = FqName("kotlinx.coroutines.flow.SharedFlow") 18 | val stateFlow = FqName("kotlinx.coroutines.flow.StateFlow") 19 | val mutableStateFlow = FqName("kotlinx.coroutines.flow.MutableStateFlow") 20 | 21 | val nativeCoroutines = FqName("com.rickclephas.kmp.nativecoroutines") 22 | val nativeFlow = FqName("com.rickclephas.kmp.nativecoroutines.NativeFlow") 23 | val nativeSuspend = FqName("com.rickclephas.kmp.nativecoroutines.NativeSuspend") 24 | 25 | val observableViewModel = FqName("com.rickclephas.kmp.observableviewmodel.ViewModel") 26 | val androidxViewModel = FqName("androidx.lifecycle.ViewModel") 27 | } 28 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/NameSuffix.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.Name 4 | 5 | internal fun Name.withSuffix(suffix: String?): Name? { 6 | if (suffix == null) return null 7 | return Name.identifier("$identifier$suffix") 8 | } 9 | 10 | internal fun Name.withoutSuffix(suffix: String?): Name? { 11 | if (suffix == null) return null 12 | val identifier = identifier 13 | if (!identifier.endsWith(suffix)) return null 14 | return Name.identifier(identifier.substring(0, identifier.length - suffix.length)) 15 | } 16 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/Names.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.Name 4 | 5 | internal object Names { 6 | object Deprecated { 7 | val message = Name.identifier("message") 8 | val replaceWith = Name.identifier("replaceWith") 9 | val level = Name.identifier("level") 10 | } 11 | object ObjCName { 12 | val name = Name.identifier("name") 13 | val swiftName = Name.identifier("swiftName") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/utils/NativeCoroutinesAnnotation.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.utils 2 | 3 | import org.jetbrains.kotlin.name.ClassId 4 | import org.jetbrains.kotlin.name.FqName 5 | 6 | public enum class NativeCoroutinesAnnotation(name: String) { 7 | NativeCoroutines("NativeCoroutines"), 8 | NativeCoroutineScope("NativeCoroutineScope"), 9 | NativeCoroutinesIgnore("NativeCoroutinesIgnore"), 10 | NativeCoroutinesRefined("NativeCoroutinesRefined"), 11 | NativeCoroutinesRefinedState("NativeCoroutinesRefinedState"), 12 | NativeCoroutinesState("NativeCoroutinesState"); 13 | 14 | public val fqName: FqName = FqName("com.rickclephas.kmp.nativecoroutines.$name") 15 | public val classId: ClassId = ClassId.topLevel(fqName) 16 | 17 | public companion object { 18 | public fun forFqName(fqName: FqName): NativeCoroutinesAnnotation? = 19 | entries.firstOrNull { it.fqName == fqName } 20 | 21 | public fun forClassId(classId: ClassId): NativeCoroutinesAnnotation? = 22 | entries.firstOrNull { it.classId == classId } 23 | } 24 | } 25 | 26 | public val NativeCoroutinesAnnotation.shouldRefineInSwift: Boolean get() = when (this) { 27 | NativeCoroutinesAnnotation.NativeCoroutinesRefined, 28 | NativeCoroutinesAnnotation.NativeCoroutinesRefinedState -> true 29 | else -> false 30 | } 31 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor: -------------------------------------------------------------------------------- 1 | com.rickclephas.kmp.nativecoroutines.compiler.KmpNativeCoroutinesCommandLineProcessor -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar: -------------------------------------------------------------------------------- 1 | com.rickclephas.kmp.nativecoroutines.compiler.KmpNativeCoroutinesCompilerPluginRegistrar 2 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/test/generated/com/rickclephas/kmp/nativecoroutines/compiler/runners/FirLightTreeCodegenTestGenerated.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.rickclephas.kmp.nativecoroutines.compiler.runners; 4 | 5 | import com.intellij.testFramework.TestDataPath; 6 | import org.jetbrains.kotlin.test.util.KtTestUtil; 7 | import org.jetbrains.kotlin.test.TargetBackend; 8 | import org.jetbrains.kotlin.test.TestMetadata; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.io.File; 12 | import java.util.regex.Pattern; 13 | 14 | /** This class is generated by {@link com.rickclephas.kmp.nativecoroutines.compiler.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ 15 | @SuppressWarnings("all") 16 | @TestMetadata("src/testData/codegen") 17 | @TestDataPath("$PROJECT_ROOT") 18 | public class FirLightTreeCodegenTestGenerated extends AbstractFirLightTreeCodegenTest { 19 | @Test 20 | public void testAllFilesPresentInCodegen() { 21 | KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), TargetBackend.JVM_IR, true); 22 | } 23 | 24 | @Test 25 | @TestMetadata("annotations.kt") 26 | public void testAnnotations() { 27 | runTest("src/testData/codegen/annotations.kt"); 28 | } 29 | 30 | @Test 31 | @TestMetadata("coroutinescope.kt") 32 | public void testCoroutinescope() { 33 | runTest("src/testData/codegen/coroutinescope.kt"); 34 | } 35 | 36 | @Test 37 | @TestMetadata("functions.kt") 38 | public void testFunctions() { 39 | runTest("src/testData/codegen/functions.kt"); 40 | } 41 | 42 | @Test 43 | @TestMetadata("properties.kt") 44 | public void testProperties() { 45 | runTest("src/testData/codegen/properties.kt"); 46 | } 47 | 48 | @Test 49 | @TestMetadata("viewmodelscope.kt") 50 | public void testViewmodelscope() { 51 | runTest("src/testData/codegen/viewmodelscope.kt"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/test/generated/com/rickclephas/kmp/nativecoroutines/compiler/runners/FirPsiCodegenTestGenerated.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.rickclephas.kmp.nativecoroutines.compiler.runners; 4 | 5 | import com.intellij.testFramework.TestDataPath; 6 | import org.jetbrains.kotlin.test.util.KtTestUtil; 7 | import org.jetbrains.kotlin.test.TargetBackend; 8 | import org.jetbrains.kotlin.test.TestMetadata; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.io.File; 12 | import java.util.regex.Pattern; 13 | 14 | /** This class is generated by {@link com.rickclephas.kmp.nativecoroutines.compiler.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ 15 | @SuppressWarnings("all") 16 | @TestMetadata("src/testData/codegen") 17 | @TestDataPath("$PROJECT_ROOT") 18 | public class FirPsiCodegenTestGenerated extends AbstractFirPsiCodegenTest { 19 | @Test 20 | public void testAllFilesPresentInCodegen() { 21 | KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), TargetBackend.JVM_IR, true); 22 | } 23 | 24 | @Test 25 | @TestMetadata("annotations.kt") 26 | public void testAnnotations() { 27 | runTest("src/testData/codegen/annotations.kt"); 28 | } 29 | 30 | @Test 31 | @TestMetadata("coroutinescope.kt") 32 | public void testCoroutinescope() { 33 | runTest("src/testData/codegen/coroutinescope.kt"); 34 | } 35 | 36 | @Test 37 | @TestMetadata("functions.kt") 38 | public void testFunctions() { 39 | runTest("src/testData/codegen/functions.kt"); 40 | } 41 | 42 | @Test 43 | @TestMetadata("properties.kt") 44 | public void testProperties() { 45 | runTest("src/testData/codegen/properties.kt"); 46 | } 47 | 48 | @Test 49 | @TestMetadata("viewmodelscope.kt") 50 | public void testViewmodelscope() { 51 | runTest("src/testData/codegen/viewmodelscope.kt"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/test/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/GenerateTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler 2 | 3 | import com.rickclephas.kmp.nativecoroutines.compiler.runners.* 4 | import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5 5 | 6 | fun main() { 7 | generateTestGroupSuiteWithJUnit5 { 8 | testGroup(testDataRoot = "src/testData", testsRoot = "src/test/generated") { 9 | val excludePattern = "^(.+)\\.fir\\.kt\$" 10 | testClass { 11 | model("diagnostics", excludedPattern = excludePattern) 12 | } 13 | testClass { 14 | model("diagnostics", excludedPattern = excludePattern) 15 | } 16 | testClass { 17 | model("diagnostics", excludedPattern = excludePattern) 18 | } 19 | 20 | testClass { 21 | model("codegen", excludedPattern = excludePattern) 22 | } 23 | testClass { 24 | model("codegen", excludedPattern = excludePattern) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/test/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/runners/AbstractClassicDiagnosticsTest.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.runners 2 | 3 | import org.jetbrains.kotlin.test.Constructor 4 | import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder 5 | import org.jetbrains.kotlin.test.builders.classicFrontendHandlersStep 6 | import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade 7 | import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact 8 | import org.jetbrains.kotlin.test.frontend.classic.handlers.ClassicDiagnosticsHandler 9 | import org.jetbrains.kotlin.test.model.* 10 | import org.jetbrains.kotlin.test.services.LibraryProvider 11 | 12 | abstract class AbstractClassicDiagnosticsTest: AbstractBaseDiagnosticsTest() { 13 | final override val targetFrontend: FrontendKind 14 | get() = FrontendKinds.ClassicFrontend 15 | final override val frontend: Constructor> 16 | get() = ::ClassicFrontendFacade 17 | final override fun TestConfigurationBuilder.handlersSetup() = classicFrontendHandlersStep { 18 | useHandlers(::ClassicDiagnosticsHandler) 19 | useAdditionalService(::LibraryProvider) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/test/kotlin/com/rickclephas/kmp/nativecoroutines/compiler/services/KmpNativeCoroutinesRuntimeClasspathProvider.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.compiler.services 2 | 3 | import org.jetbrains.kotlin.test.model.TestModule 4 | import org.jetbrains.kotlin.test.services.RuntimeClasspathProvider 5 | import org.jetbrains.kotlin.test.services.TestServices 6 | import java.io.File 7 | 8 | internal sealed class KmpNativeCoroutinesRuntimeClasspathProvider( 9 | testServices: TestServices, 10 | private val propertyKey: String 11 | ): RuntimeClasspathProvider(testServices) { 12 | override fun runtimeClassPaths(module: TestModule): List = 13 | System.getProperty(propertyKey).split(File.pathSeparator).map(::File) 14 | } 15 | 16 | internal class KmpNativeCoroutinesJvmRuntimeClasspathProvider( 17 | testServices: TestServices 18 | ): KmpNativeCoroutinesRuntimeClasspathProvider( 19 | testServices, "com.rickclephas.kmp.nativecoroutines.test.classpath-jvm" 20 | ) 21 | 22 | internal class KmpNativeCoroutinesNativeRuntimeClasspathProvider( 23 | testServices: TestServices 24 | ): KmpNativeCoroutinesRuntimeClasspathProvider( 25 | testServices, "com.rickclephas.kmp.nativecoroutines.test.classpath-native" 26 | ) 27 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/codegen/annotations.box.txt: -------------------------------------------------------------------------------- 1 | OK1 2 | OK2 3 | OK4 4 | OK5 5 | OK7; 6 | OK7 7 | OK8 8 | OK9 9 | OK10 10 | OK11; 11 | OK11 12 | OK12; 13 | OK12 14 | OK13 15 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/codegen/coroutinescope.box.txt: -------------------------------------------------------------------------------- 1 | OK1 2 | OK2 3 | OK3 4 | OK4 5 | OK5 6 | OK6 7 | OK7 8 | OK8 9 | OK9 10 | OK10 11 | OK11 12 | OK12 13 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/codegen/functions.box.txt: -------------------------------------------------------------------------------- 1 | OK1 2 | null 3 | OK3 4 | null 5 | null 6 | null 7 | OK7; 8 | OK8 9 | OK9 10 | 9 11 | OK10 12 | OK11 13 | OK12 14 | OK13 15 | OK14 16 | OK15 17 | OK16 18 | OK17 19 | null 20 | OK19 21 | OK20 22 | null 23 | OK22 24 | OK23 25 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/codegen/properties.box.txt: -------------------------------------------------------------------------------- 1 | OK1 2 | OK2; 3 | OK2 4 | OK3; 5 | OK3 6 | OK4; 7 | OK4 8 | null 9 | null; 10 | null 11 | null; 12 | null 13 | null 14 | null 15 | null 16 | null 17 | null 18 | null 19 | null 20 | null 21 | null 22 | null 23 | OK14 24 | OK15; 25 | OK15 26 | OK16; 27 | OK16 28 | OK17 29 | OK18; 30 | OK18 31 | OK19; 32 | OK19 33 | OK20 34 | OK21; 35 | OK21 36 | OK22; 37 | OK22 38 | OK23 39 | OK23; 40 | OK24 41 | OK24; 42 | OK25 43 | OK26 44 | OK26; 45 | OK27 46 | OK27; 47 | OK28 48 | null 49 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/codegen/viewmodelscope.box.txt: -------------------------------------------------------------------------------- 1 | OK1 2 | OK2 3 | OK3 4 | OK4 5 | OK5 6 | OK6 7 | OK7 8 | OK8 9 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/diagnostics/conflict.kt: -------------------------------------------------------------------------------- 1 | // FIR_IDENTICAL 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefined 5 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefinedState 6 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState 7 | import kotlinx.coroutines.flow.StateFlow 8 | 9 | @NativeCoroutines 10 | val stateFlowPropertyA: StateFlow get() = throw Throwable() 11 | 12 | @NativeCoroutinesRefined 13 | val stateFlowPropertyB: StateFlow get() = throw Throwable() 14 | 15 | @NativeCoroutinesRefinedState 16 | val stateFlowPropertyC: StateFlow get() = throw Throwable() 17 | 18 | @NativeCoroutinesState 19 | val stateFlowPropertyD: StateFlow get() = throw Throwable() 20 | 21 | @NativeCoroutines 22 | @NativeCoroutinesRefined 23 | val stateFlowPropertyE: StateFlow get() = throw Throwable() 24 | 25 | @NativeCoroutines 26 | @NativeCoroutinesRefinedState 27 | val stateFlowPropertyF: StateFlow get() = throw Throwable() 28 | 29 | @NativeCoroutines 30 | @NativeCoroutinesState 31 | val stateFlowPropertyG: StateFlow get() = throw Throwable() 32 | 33 | @NativeCoroutinesRefined 34 | @NativeCoroutinesRefinedState 35 | val stateFlowPropertyH: StateFlow get() = throw Throwable() 36 | 37 | @NativeCoroutinesRefined 38 | @NativeCoroutinesState 39 | val stateFlowPropertyI: StateFlow get() = throw Throwable() 40 | 41 | @NativeCoroutinesRefinedState 42 | @NativeCoroutinesState 43 | val stateFlowPropertyJ: StateFlow get() = throw Throwable() 44 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/diagnostics/ignored.kt: -------------------------------------------------------------------------------- 1 | // FIR_IDENTICAL 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore 5 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefined 6 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefinedState 7 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState 8 | import kotlinx.coroutines.flow.StateFlow 9 | 10 | @NativeCoroutines 11 | suspend fun suspendFunctionA(): Int = 0 12 | 13 | @NativeCoroutinesIgnore 14 | suspend fun suspendFunctionB(): Int = 0 15 | 16 | @NativeCoroutines 17 | @NativeCoroutinesIgnore 18 | suspend fun suspendFunctionC(): Int = 0 19 | 20 | @NativeCoroutinesRefined 21 | suspend fun suspendFunctionD(): Int = 0 22 | 23 | @NativeCoroutinesIgnore 24 | suspend fun suspendFunctionE(): Int = 0 25 | 26 | @NativeCoroutinesRefined 27 | @NativeCoroutinesIgnore 28 | suspend fun suspendFunctionF(): Int = 0 29 | 30 | @NativeCoroutinesRefinedState 31 | val stateFlowPropertyA: StateFlow get() = throw Throwable() 32 | 33 | @NativeCoroutinesIgnore 34 | val stateFlowPropertyB: StateFlow get() = throw Throwable() 35 | 36 | @NativeCoroutinesRefinedState 37 | @NativeCoroutinesIgnore 38 | val stateFlowPropertyC: StateFlow get() = throw Throwable() 39 | 40 | @NativeCoroutinesState 41 | val stateFlowPropertyD: StateFlow get() = throw Throwable() 42 | 43 | @NativeCoroutinesIgnore 44 | val stateFlowPropertyE: StateFlow get() = throw Throwable() 45 | 46 | @NativeCoroutinesState 47 | @NativeCoroutinesIgnore 48 | val stateFlowPropertyF: StateFlow get() = throw Throwable() 49 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/diagnostics/implicitReturnTypeK2.kt: -------------------------------------------------------------------------------- 1 | // FIR_IDENTICAL 2 | // K2_MODE 3 | 4 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 5 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore 6 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | 9 | @NativeCoroutines 10 | suspend fun implicitReturnTypeFunction() = 0 11 | 12 | @NativeCoroutines 13 | suspend fun explicitReturnTypeFunction(): Int = 0 14 | 15 | @NativeCoroutines 16 | suspend fun implicitUnitReturnTypeFunction() { } 17 | 18 | @NativeCoroutinesIgnore 19 | suspend fun ignoredImplicitReturnTypeFunction() = 0 20 | 21 | @NativeCoroutinesState 22 | val implicitReturnTypeProperty = MutableStateFlow(0) 23 | 24 | @NativeCoroutinesState 25 | val explicitReturnTypeProperty: MutableStateFlow = MutableStateFlow(0) 26 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/diagnostics/implicitReturnTypeKSP.kt: -------------------------------------------------------------------------------- 1 | // FIR_IDENTICAL 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore 5 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | 8 | @NativeCoroutines 9 | suspend fun implicitReturnTypeFunction() = 0 10 | 11 | @NativeCoroutines 12 | suspend fun explicitReturnTypeFunction(): Int = 0 13 | 14 | @NativeCoroutines 15 | suspend fun implicitUnitReturnTypeFunction() { } 16 | 17 | @NativeCoroutinesIgnore 18 | suspend fun ignoredImplicitReturnTypeFunction() = 0 19 | 20 | @NativeCoroutinesState 21 | val implicitReturnTypeProperty = MutableStateFlow(0) 22 | 23 | @NativeCoroutinesState 24 | val explicitReturnTypeProperty: MutableStateFlow = MutableStateFlow(0) 25 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-compiler/src/testData/diagnostics/unsupported.kt: -------------------------------------------------------------------------------- 1 | // FIR_IDENTICAL 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore 5 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefined 6 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesRefinedState 7 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState 8 | import kotlinx.coroutines.flow.StateFlow 9 | 10 | class TestClass { 11 | 12 | @NativeCoroutines 13 | val Int.extensionPropertyA: StateFlow get() = throw Throwable() 14 | 15 | @NativeCoroutinesIgnore 16 | val Int.extensionPropertyE: StateFlow get() = throw Throwable() 17 | 18 | @NativeCoroutinesRefined 19 | val Int.extensionPropertyB: StateFlow get() = throw Throwable() 20 | 21 | @NativeCoroutinesRefinedState 22 | val Int.extensionPropertyC: StateFlow get() = throw Throwable() 23 | 24 | @NativeCoroutinesState 25 | val Int.extensionPropertyD: StateFlow get() = throw Throwable() 26 | } 27 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/api/kmp-nativecoroutines-core.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickclephas/KMP-NativeCoroutines/26487987d20ef9b5c8972eba72fac01a5200477c/kmp-nativecoroutines-core/api/kmp-nativecoroutines-core.api -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/api/kmp-nativecoroutines-core.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] 3 | // Alias: apple => [iosArm64, iosSimulatorArm64, iosX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] 4 | // Rendering settings: 5 | // - Signature version: 2 6 | // - Show manifest properties: true 7 | // - Show declarations: true 8 | 9 | // Library unique name: 10 | // Targets: [apple] 11 | final fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).com.rickclephas.kmp.nativecoroutines/asNativeFlow(kotlinx.coroutines/CoroutineScope? = ...): kotlin/Function3, kotlin/Unit, kotlin/Unit>, kotlin/Function2, kotlin/Function2, kotlin/Function0> // com.rickclephas.kmp.nativecoroutines/asNativeFlow|asNativeFlow@kotlinx.coroutines.flow.Flow<0:0>(kotlinx.coroutines.CoroutineScope?){0§}[0] 12 | // Targets: [apple] 13 | final fun <#A: kotlin/Any?> com.rickclephas.kmp.nativecoroutines/nativeSuspend(kotlinx.coroutines/CoroutineScope? = ..., kotlin.coroutines/SuspendFunction0<#A>): kotlin/Function3, kotlin/Function2, kotlin/Function2, kotlin/Function0> // com.rickclephas.kmp.nativecoroutines/nativeSuspend|nativeSuspend(kotlinx.coroutines.CoroutineScope?;kotlin.coroutines.SuspendFunction0<0:0>){0§}[0] 14 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeErrorApple.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlinx.cinterop.ExperimentalForeignApi 4 | import kotlinx.cinterop.UnsafeNumber 5 | import kotlinx.cinterop.convert 6 | import platform.Foundation.NSError 7 | import platform.Foundation.NSLocalizedDescriptionKey 8 | 9 | public actual typealias NativeError = NSError 10 | 11 | /** 12 | * Converts a [Throwable] to a [NSError]. 13 | * 14 | * The returned [NSError] has `KotlinException` as the [NSError.domain], `0` as the [NSError.code] and 15 | * the [NSError.localizedDescription] is set to the [Throwable.message]. 16 | * 17 | * The Kotlin throwable can be retrieved from the [NSError.userInfo] with the key `KotlinException`. 18 | */ 19 | @OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) 20 | internal actual fun Throwable.asNativeError(): NativeError { 21 | val userInfo = mutableMapOf() 22 | userInfo["KotlinException"] = this 23 | val message = message 24 | if (message != null) { 25 | userInfo[NSLocalizedDescriptionKey] = message 26 | } 27 | return NSError.errorWithDomain("KotlinException", 0.convert(), userInfo) 28 | } 29 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/appleTest/kotlin/com/rickclephas/kmp/nativecoroutines/KotlinCauseApple.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | internal actual val NativeError.kotlinCause: Throwable? 4 | get() = this.userInfo["KotlinException"] as? Throwable 5 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/appleTest/kotlin/com/rickclephas/kmp/nativecoroutines/NativeErrorAppleTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlinx.cinterop.ExperimentalForeignApi 4 | import kotlinx.cinterop.UnsafeNumber 5 | import kotlinx.cinterop.convert 6 | import kotlin.test.* 7 | 8 | class NativeErrorAppleTests { 9 | 10 | @Test 11 | @OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) 12 | fun ensureNSErrorDomainAndCodeAreCorrect() { 13 | val exception = RandomException() 14 | val nsError = exception.asNativeError() 15 | assertEquals("KotlinException", nsError.domain, "Incorrect NSError domain") 16 | assertEquals(0.convert(), nsError.code, "Incorrect NSError code") 17 | } 18 | 19 | @Test 20 | fun ensureLocalizedDescriptionIsSetToMessage() { 21 | val exception = RandomException() 22 | val nsError = exception.asNativeError() 23 | assertEquals(exception.message, nsError.localizedDescription, 24 | "Localized description isn't set to message") 25 | } 26 | 27 | @Test 28 | fun ensureExceptionIsPartOfUserInfo() { 29 | val exception = RandomException() 30 | val nsError = exception.asNativeError() 31 | assertSame(exception, nsError.userInfo["KotlinException"], "Exception isn't part of the user info") 32 | assertSame(exception, nsError.kotlinCause, "Incorrect kotlinCause") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/Empty.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | /** 4 | * The core library only contains code for Apple targets. 5 | * There is an issue with publication for other targets without source code though. 6 | * So this private constant will make sure all targets have source code. 7 | */ 8 | @Suppress("unused") 9 | private const val EMPTY = 1 -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/commonTest/kotlin/com/rickclephas/kmp/nativecoroutines/RandomException.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.random.Random 4 | 5 | /** 6 | * An exception with a message consisting of 20 random capital letter. 7 | */ 8 | internal class RandomException: Exception( 9 | (1..20).map { Random.nextInt(65, 91).toChar() }.joinToString() 10 | ) -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/commonTest/kotlin/com/rickclephas/kmp/nativecoroutines/RandomValue.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.random.Random 4 | 5 | /** 6 | * A data class containing a random integer. 7 | */ 8 | internal data class RandomValue( 9 | val randomInt: Int = Random.nextInt() 10 | ) 11 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/compilerTestMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeErrorJvm.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | public actual typealias NativeError = Throwable 4 | 5 | internal actual fun Throwable.asNativeError(): Throwable = this 6 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/compilerTestTest/kotlin/com/rickclephas/kmp/nativecoroutines/KotlinCauseJvm.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | internal actual val NativeError.kotlinCause: Throwable? get() = this 4 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/jvmMain/resources/META-INF/com/rickclephas/kmp/kmp-nativecoroutines-core-jvm/verification.properties: -------------------------------------------------------------------------------- 1 | #This is the verification token for the com.rickclephas.kmp:kmp-nativecoroutines-core-jvm SDK. 2 | #Sun Jun 02 04:07:03 PDT 2024 3 | token=W3RYNAJ7DVG75HELMMNMMEQFQY 4 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/CoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.SupervisorJob 6 | 7 | /** 8 | * The default [CoroutineScope] used if no specific scope is provided. 9 | */ 10 | internal val defaultCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCallback.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | /** 4 | * A callback with a single argument. 5 | * 6 | * We don't want the Swift code to know how to get the [Unit] object, so we'll provide it as the second argument. 7 | * This way Swift can just return the value that it received without knowing what it is/how to get it. 8 | */ 9 | public typealias NativeCallback = (T, Unit) -> Unit 10 | 11 | /** 12 | * Invokes the callback with the specified [value]. 13 | */ 14 | internal inline operator fun NativeCallback.invoke(value: T) = 15 | invoke(value, Unit) 16 | 17 | /** 18 | * A callback with two arguments. 19 | * 20 | * We don't want the Swift code to know how to get the [Unit] object, so we'll provide it as the third argument. 21 | * This way Swift can just return the value that it received without knowing what it is/how to get it. 22 | */ 23 | public typealias NativeCallback2 = (T1, T2, Unit) -> Unit 24 | 25 | /** 26 | * Invokes the callback with the specified [value1] and [value2]. 27 | */ 28 | internal inline operator fun NativeCallback2.invoke(value1: T1, value2: T2) = 29 | invoke(value1, value2, Unit) 30 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCancellable.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlinx.coroutines.Job 4 | 5 | /** 6 | * A function that cancels the coroutines [Job]. 7 | */ 8 | public typealias NativeCancellable = () -> Unit 9 | 10 | /** 11 | * Creates a [NativeCancellable] for this [Job]. 12 | * 13 | * The returned cancellable will cancel the job without a cause. 14 | * @see Job.cancel 15 | */ 16 | internal inline fun Job.asNativeCancellable(): NativeCancellable = { cancel() } 17 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeError.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | /** 4 | * Represents an error in a way that the specific platform is able to handle 5 | */ 6 | public expect class NativeError 7 | 8 | /** 9 | * Converts a [Throwable] to a [NativeError]. 10 | */ 11 | internal expect fun Throwable.asNativeError(): NativeError 12 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesTest/kotlin/com/rickclephas/kmp/nativecoroutines/KotlinCause.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | /** 4 | * Gets the [Throwable] from the [NativeError]. 5 | * 6 | * Note: this doesn't actually convert a [NativeError] to a [Throwable], 7 | * it should only be used to test [asNativeError] logic. 8 | */ 9 | internal expect val NativeError.kotlinCause: Throwable? -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesTest/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCallbackTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlin.test.* 4 | 5 | class NativeCallbackTests { 6 | 7 | @Test 8 | fun ensureInvoked() { 9 | var invokeCount = 0 10 | var receivedValue: RandomValue? = null 11 | val callback: NativeCallback = callback@{ value, unit -> 12 | receivedValue = value 13 | invokeCount++ 14 | // This isn't required in Kotlin, but it is in Swift, so we'll test it anyway 15 | return@callback unit 16 | } 17 | val value = RandomValue() 18 | callback(value) 19 | assertEquals(1, invokeCount, "NativeCallback should have been invoked once") 20 | assertSame(value, receivedValue, "Received value should be the same as the send value") 21 | } 22 | 23 | @Test 24 | fun ensureInvoked2() { 25 | var invokeCount = 0 26 | var receivedValue1: RandomValue? = null 27 | var receivedValue2: RandomValue? = null 28 | val callback: NativeCallback2 = callback@{ value1, value2, unit -> 29 | receivedValue1 = value1 30 | receivedValue2 = value2 31 | invokeCount++ 32 | // This isn't required in Kotlin, but it is in Swift, so we'll test it anyway 33 | return@callback unit 34 | } 35 | val value1 = RandomValue() 36 | val value2 = RandomValue() 37 | callback(value1, value2) 38 | assertEquals(1, invokeCount, "NativeCallback should have been invoked once") 39 | assertSame(value1, receivedValue1, "Received value 1 should be the same as send value 1") 40 | assertSame(value2, receivedValue2, "Received value 2 should be the same as send value 2") 41 | } 42 | } -------------------------------------------------------------------------------- /kmp-nativecoroutines-core/src/nativeCoroutinesTest/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCancellableTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines 2 | 3 | import kotlinx.coroutines.Job 4 | import kotlin.test.* 5 | 6 | class NativeCancellableTests { 7 | 8 | @Test 9 | fun ensureThatTheJobGetsCancelled() { 10 | val job = Job() 11 | val nativeCancellable = job.asNativeCancellable() 12 | assertFalse(job.isCancelled, "Job shouldn't be cancelled yet") 13 | nativeCancellable() 14 | assertTrue(job.isCancelled, "Job should be cancelled") 15 | } 16 | } -------------------------------------------------------------------------------- /kmp-nativecoroutines-gradle-plugin/Version.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.gradle 2 | 3 | internal const val VERSION = "$version" -------------------------------------------------------------------------------- /kmp-nativecoroutines-gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | `java-gradle-plugin` 5 | id("kmp-nativecoroutines-kotlin-jvm") 6 | alias(libs.plugins.gradle.plugin.publish) 7 | id("kmp-nativecoroutines-publish") 8 | } 9 | 10 | kotlin { 11 | explicitApi() 12 | jvmToolchain(11) 13 | } 14 | 15 | java { 16 | withSourcesJar() 17 | } 18 | 19 | val copyVersionTemplate by tasks.registering(Copy::class) { 20 | inputs.property("version", version) 21 | from(layout.projectDirectory.file("Version.kt")) 22 | into(layout.buildDirectory.dir("generated/kmp-nativecoroutines-version/main")) 23 | expand("version" to "$version") 24 | filteringCharset = "UTF-8" 25 | } 26 | 27 | tasks.compileKotlin { 28 | dependsOn(copyVersionTemplate) 29 | } 30 | 31 | val sourcesJar by tasks.getting(Jar::class) { 32 | dependsOn(copyVersionTemplate) 33 | } 34 | 35 | sourceSets { 36 | main { 37 | java.srcDir(layout.buildDirectory.dir("generated/kmp-nativecoroutines-version/main")) 38 | } 39 | } 40 | 41 | gradlePlugin { 42 | website = "https://github.com/rickclephas/KMP-NativeCoroutines" 43 | vcsUrl = "https://github.com/rickclephas/KMP-NativeCoroutines" 44 | plugins { 45 | create("kmpNativeCoroutines") { 46 | id = "com.rickclephas.kmp.nativecoroutines" 47 | displayName = "KMP-NativeCoroutines" 48 | description = "Swift library for Kotlin Coroutines" 49 | implementationClass = "com.rickclephas.kmp.nativecoroutines.gradle.KmpNativeCoroutinesPlugin" 50 | tags = listOf("kotlin", "swift", "native", "coroutines") 51 | } 52 | } 53 | } 54 | 55 | dependencies { 56 | implementation(libs.kotlin.gradle.plugin) 57 | implementation(libs.ksp.gradle.plugin) 58 | } 59 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-gradle-plugin/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/gradle/KspCommandLineArgumentProvider.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.gradle 2 | 3 | import org.gradle.process.CommandLineArgumentProvider 4 | 5 | internal class KspCommandLineArgumentProvider( 6 | private val nativeCoroutines: KmpNativeCoroutinesExtension 7 | ): CommandLineArgumentProvider { 8 | override fun asArguments(): Iterable = listOfNotNull( 9 | "nativeCoroutines.suffix=${nativeCoroutines.suffix}", 10 | nativeCoroutines.fileSuffix?.let { "nativeCoroutines.fileSuffix=$it" }, 11 | nativeCoroutines.flowValueSuffix?.let { "nativeCoroutines.flowValueSuffix=$it" }, 12 | nativeCoroutines.flowReplayCacheSuffix?.let { "nativeCoroutines.flowReplayCacheSuffix=$it" }, 13 | "nativeCoroutines.stateSuffix=${nativeCoroutines.stateSuffix}", 14 | nativeCoroutines.stateFlowSuffix?.let { "nativeCoroutines.stateFlowSuffix=$it" }, 15 | "nativeCoroutines.k2Mode=${nativeCoroutines.k2Mode}", 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.stdlib.default.dependency=false 2 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/idea/compiler/extensions/KmpNativeCoroutinesCompilerPluginProvider.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.idea.compiler.extensions 2 | 3 | import com.intellij.openapi.application.PathManager 4 | import com.intellij.openapi.project.Project 5 | import com.rickclephas.kmp.nativecoroutines.compiler.KmpNativeCoroutinesCompilerPluginRegistrar 6 | import org.jetbrains.kotlin.idea.fir.extensions.CompilerPluginRegistrarUtils 7 | import org.jetbrains.kotlin.idea.fir.extensions.KotlinBundledFirCompilerPluginProvider 8 | import java.nio.file.Path 9 | 10 | public class KmpNativeCoroutinesCompilerPluginProvider: KotlinBundledFirCompilerPluginProvider { 11 | 12 | private val registrarClass = KmpNativeCoroutinesCompilerPluginRegistrar::class 13 | 14 | override fun provideBundledPluginJar(project: Project, userSuppliedPluginJar: Path): Path? { 15 | val registrarContent = CompilerPluginRegistrarUtils.readRegistrarContent(userSuppliedPluginJar) ?: return null 16 | if (registrarContent.trim() != registrarClass.qualifiedName) return null 17 | return PathManager.getJarForClass(registrarClass.java) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/idea/gradle/KmpNativeCoroutinesModel.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.idea.gradle 2 | 3 | import com.intellij.openapi.externalSystem.model.Key 4 | import java.io.Serializable 5 | 6 | public interface KmpNativeCoroutinesModel: Serializable { 7 | public val suffix: String 8 | public val flowValueSuffix: String? 9 | public val flowReplayCacheSuffix: String? 10 | public val stateSuffix: String 11 | public val stateFlowSuffix: String? 12 | public val exposedSeverity: String 13 | public val generatedSourceDirs: List 14 | public val k2Mode: Boolean 15 | } 16 | 17 | internal class KmpNativeCoroutinesModelImpl( 18 | override val suffix: String, 19 | override val flowValueSuffix: String?, 20 | override val flowReplayCacheSuffix: String?, 21 | override val stateSuffix: String, 22 | override val stateFlowSuffix: String?, 23 | override val exposedSeverity: String, 24 | override val generatedSourceDirs: List, 25 | override val k2Mode: Boolean, 26 | ): KmpNativeCoroutinesModel 27 | 28 | internal val KmpNativeCoroutinesModelKey = Key(KmpNativeCoroutinesModel::class.java.name, 1) 29 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/idea/gradle/KmpNativeCoroutinesProjectResolverExtension.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.idea.gradle 2 | 3 | import com.intellij.openapi.externalSystem.model.DataNode 4 | import com.intellij.openapi.externalSystem.model.project.ModuleData 5 | import org.gradle.tooling.model.idea.IdeaModule 6 | import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension 7 | 8 | public class KmpNativeCoroutinesProjectResolverExtension: AbstractProjectResolverExtension() { 9 | 10 | override fun getExtraProjectModelClasses(): Set> = 11 | setOf(KmpNativeCoroutinesModel::class.java) 12 | 13 | override fun getToolingExtensionsClasses(): Set> = 14 | setOf(KmpNativeCoroutinesModelBuilderService::class.java, Unit::class.java) 15 | 16 | override fun populateModuleExtraModels(gradleModule: IdeaModule, ideModule: DataNode) { 17 | resolverCtx.getExtraProject(gradleModule, KmpNativeCoroutinesModel::class.java)?.let { 18 | ideModule.createChild(KmpNativeCoroutinesModelKey, it) 19 | } 20 | super.populateModuleExtraModels(gradleModule, ideModule) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/idea/quickfixes/k1/RemoveAnnotationFixFactory.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.idea.quickfixes.k1 2 | 3 | import com.intellij.codeInsight.intention.IntentionAction 4 | import org.jetbrains.kotlin.diagnostics.Diagnostic 5 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 6 | import org.jetbrains.kotlin.idea.inspections.RemoveAnnotationFix 7 | import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory 8 | import org.jetbrains.kotlin.idea.quickfix.QuickFixes 9 | import org.jetbrains.kotlin.psi.KtAnnotationEntry 10 | import org.jetbrains.kotlin.psi.KtElement 11 | 12 | internal class RemoveAnnotationFixFactory( 13 | private val diagnosticFactories: Array> 14 | ): KotlinSingleIntentionActionFactory() { 15 | 16 | internal companion object { 17 | fun QuickFixes.registerRemoveAnnotationFix( 18 | vararg diagnosticFactories: DiagnosticFactory0 19 | ) { 20 | val factory = RemoveAnnotationFixFactory(diagnosticFactories) 21 | diagnosticFactories.forEach { register(it, factory) } 22 | } 23 | } 24 | 25 | override fun createAction(diagnostic: Diagnostic): IntentionAction? { 26 | val diagnosticFactory = diagnosticFactories.firstOrNull { it == diagnostic.factory } ?: return null 27 | val annotationEntry = diagnosticFactory.cast(diagnostic).psiElement as? KtAnnotationEntry ?: return null 28 | val annotationName = annotationEntry.shortName?.identifierOrNullIfSpecial ?: return null 29 | return RemoveAnnotationFix("Remove @$annotationName annotation", annotationEntry) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/idea/quickfixes/k2/RemoveAnnotationFixFactory.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.idea.quickfixes.k2 2 | 3 | import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaCompilerPluginDiagnostic0 4 | import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory0 5 | import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory 6 | import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KtQuickFixesListBuilder 7 | import org.jetbrains.kotlin.idea.inspections.RemoveAnnotationFix 8 | import org.jetbrains.kotlin.psi.KtAnnotationEntry 9 | 10 | internal class RemoveAnnotationFixFactory( 11 | private val diagnosticFactories: Array 12 | ) { 13 | 14 | internal companion object { 15 | fun KtQuickFixesListBuilder.registerRemoveAnnotationFix( 16 | vararg diagnosticFactories: KtDiagnosticFactory0 17 | ) { 18 | val factory = RemoveAnnotationFixFactory(diagnosticFactories) 19 | registerFactory(factory.intentionBased) 20 | } 21 | } 22 | 23 | private val intentionBased = KotlinQuickFixFactory.IntentionBased { diagnostic: KaCompilerPluginDiagnostic0 -> 24 | if (diagnosticFactories.none { it.name == diagnostic.factoryName }) return@IntentionBased emptyList() 25 | val annotationEntry = diagnostic.psi as? KtAnnotationEntry ?: return@IntentionBased emptyList() 26 | val annotationName = annotationEntry.shortName?.identifierOrNullIfSpecial ?: return@IntentionBased emptyList() 27 | listOf(RemoveAnnotationFix("Remove @$annotationName annotation", annotationEntry)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.intellij.modules.platform 4 | org.jetbrains.kotlin 5 | com.intellij.gradle 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-idea-plugin/src/main/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService: -------------------------------------------------------------------------------- 1 | com.rickclephas.kmp.nativecoroutines.idea.gradle.KmpNativeCoroutinesModelBuilderService 2 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/api/kmp-nativecoroutines-ksp.api: -------------------------------------------------------------------------------- 1 | public final class com/rickclephas/kmp/nativecoroutines/ksp/KmpNativeCoroutinesSymbolProcessorProvider : com/google/devtools/ksp/processing/SymbolProcessorProvider { 2 | public fun ()V 3 | public fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lcom/google/devtools/ksp/processing/SymbolProcessor; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-nativecoroutines-kotlin-jvm") 3 | id("kmp-nativecoroutines-publish") 4 | } 5 | 6 | dependencies { 7 | implementation(libs.ksp.api) 8 | implementation(libs.kotlinpoet) 9 | implementation(libs.kotlinpoet.ksp) 10 | testImplementation(libs.kotlin.test) 11 | testImplementation(libs.kotlinCompileTesting.ksp) 12 | testImplementation(libs.kotlinx.coroutines.core) 13 | testImplementation(project(":kmp-nativecoroutines-annotations")) 14 | } 15 | 16 | kotlin { 17 | explicitApi() 18 | jvmToolchain(11) 19 | } 20 | 21 | tasks.compileKotlin.configure { 22 | compilerOptions { 23 | freeCompilerArgs.add("-Xjvm-default=all") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/KmpNativeCoroutinesOptions.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp 2 | 3 | internal class KmpNativeCoroutinesOptions( 4 | options: Map 5 | ) { 6 | val suffix = options["nativeCoroutines.suffix"] ?: error("Missing required option: suffix") 7 | val fileSuffix = options["nativeCoroutines.fileSuffix"] ?: suffix 8 | val flowValueSuffix = options["nativeCoroutines.flowValueSuffix"] 9 | val flowReplayCacheSuffix = options["nativeCoroutines.flowReplayCacheSuffix"] 10 | val stateSuffix = options["nativeCoroutines.stateSuffix"] ?: error("Missing required option: stateSuffix") 11 | val stateFlowSuffix = options["nativeCoroutines.stateFlowSuffix"] 12 | val k2Mode = options["nativeCoroutines.k2Mode"]?.toBooleanStrict() ?: false 13 | } 14 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/KmpNativeCoroutinesSymbolProcessorProvider.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessor 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 6 | 7 | public class KmpNativeCoroutinesSymbolProcessorProvider: SymbolProcessorProvider { 8 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { 9 | val options = KmpNativeCoroutinesOptions(environment.options) 10 | return KmpNativeCoroutinesSymbolProcessor(environment.codeGenerator, environment.logger, options) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/Names.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.MemberName 5 | 6 | private const val packageName = "com.rickclephas.kmp.nativecoroutines" 7 | 8 | internal const val nativeCoroutinesAnnotationName = "$packageName.NativeCoroutines" 9 | internal const val nativeCoroutinesStateAnnotationName = "$packageName.NativeCoroutinesState" 10 | internal const val nativeCoroutinesRefinedAnnotationName = "$packageName.NativeCoroutinesRefined" 11 | internal const val nativeCoroutinesRefinedStateAnnotationName = "$packageName.NativeCoroutinesRefinedState" 12 | internal const val nativeCoroutineScopeAnnotationName = "$packageName.NativeCoroutineScope" 13 | 14 | internal val nativeSuspendMemberName = MemberName(packageName, "nativeSuspend") 15 | internal val nativeSuspendClassName = ClassName(packageName, "NativeSuspend") 16 | 17 | internal val asNativeFlowMemberName = MemberName(packageName, "asNativeFlow") 18 | internal val nativeFlowClassName = ClassName(packageName, "NativeFlow") 19 | 20 | internal val runMemberName = MemberName("kotlin", "run") 21 | internal val objCNameAnnotationClassName = ClassName("kotlin.native", "ObjCName") 22 | internal val optInAnnotationClassName = ClassName("kotlin", "OptIn") 23 | internal val shouldRefineInSwiftAnnotationClassName = ClassName("kotlin.native", "ShouldRefineInSwift") 24 | internal const val throwsAnnotationName = "kotlin.Throws" 25 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/kotlinpoet/ParameterSpec.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp.kotlinpoet 2 | 3 | import com.google.devtools.ksp.symbol.KSValueParameter 4 | import com.rickclephas.kmp.nativecoroutines.ksp.toAnnotationSpecs 5 | import com.squareup.kotlinpoet.KModifier 6 | import com.squareup.kotlinpoet.ParameterSpec 7 | import com.squareup.kotlinpoet.ksp.TypeParameterResolver 8 | import com.squareup.kotlinpoet.ksp.toTypeName 9 | 10 | internal fun List.toParameterSpecs( 11 | typeParameterResolver: TypeParameterResolver 12 | ) = map { parameter -> 13 | val name = parameter.name?.asString() ?: "" 14 | val type = parameter.type.toTypeName(typeParameterResolver) 15 | val builder = ParameterSpec.builder(name, type) 16 | builder.addAnnotations(parameter.annotations.toAnnotationSpecs()) 17 | if (parameter.isVararg) { 18 | builder.addModifiers(KModifier.VARARG) 19 | } 20 | // TODO: Add default value once those are exported to ObjC 21 | builder.build() 22 | } 23 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/kotlinpoet/TypeName.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp.kotlinpoet 2 | 3 | import com.google.devtools.ksp.symbol.KSClassDeclaration 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.ParameterizedTypeName 6 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 7 | import com.squareup.kotlinpoet.TypeName 8 | import com.squareup.kotlinpoet.ksp.TypeParameterResolver 9 | import com.squareup.kotlinpoet.ksp.toClassName 10 | 11 | internal fun KSClassDeclaration.toTypeName( 12 | typeParameterResolver: TypeParameterResolver 13 | ): TypeName { 14 | val className = toClassName() 15 | val typeParams = typeParameters 16 | return when { 17 | typeParams.isEmpty() -> className 18 | else -> className.parameterizedBy(typeParams.toTypeVariableNames(typeParameterResolver)) 19 | } 20 | } 21 | 22 | internal val TypeName.canonicalClassName: String? get() = when (this) { 23 | is ClassName -> canonicalName 24 | is ParameterizedTypeName -> rawType.canonicalName 25 | else -> null 26 | } 27 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/kotlinpoet/TypeParameterResolver.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp.kotlinpoet 2 | 3 | import com.google.devtools.ksp.symbol.KSDeclaration 4 | import com.squareup.kotlinpoet.ksp.TypeParameterResolver 5 | import com.squareup.kotlinpoet.ksp.toTypeParameterResolver 6 | 7 | internal fun KSDeclaration.getTypeParameterResolver(): TypeParameterResolver = 8 | typeParameters.toTypeParameterResolver(parentDeclaration?.getTypeParameterResolver()) 9 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/kotlin/com/rickclephas/kmp/nativecoroutines/ksp/kotlinpoet/TypeVariableName.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.ksp.kotlinpoet 2 | 3 | import com.google.devtools.ksp.symbol.KSTypeParameter 4 | import com.google.devtools.ksp.symbol.Variance 5 | import com.squareup.kotlinpoet.KModifier 6 | import com.squareup.kotlinpoet.TypeVariableName 7 | import com.squareup.kotlinpoet.ksp.TypeParameterResolver 8 | import com.squareup.kotlinpoet.ksp.toTypeName 9 | 10 | internal fun List.toTypeVariableNames( 11 | typeParameterResolver: TypeParameterResolver, 12 | withoutVariance: Boolean = false 13 | ): List = map { it.toTypeVariableName(typeParameterResolver, withoutVariance) } 14 | 15 | internal fun KSTypeParameter.toTypeVariableName( 16 | typeParamResolver: TypeParameterResolver, 17 | withoutVariance: Boolean = false 18 | ): TypeVariableName { 19 | val typeVarName = name.getShortName() 20 | val typeVarBounds = bounds.map { it.toTypeName(typeParamResolver) }.toList() 21 | val typeVarVariance = when { 22 | withoutVariance -> null 23 | variance == Variance.COVARIANT -> KModifier.OUT 24 | variance == Variance.CONTRAVARIANT -> KModifier.IN 25 | else -> null 26 | } 27 | return TypeVariableName(typeVarName, bounds = typeVarBounds, variance = typeVarVariance) 28 | } 29 | -------------------------------------------------------------------------------- /kmp-nativecoroutines-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | com.rickclephas.kmp.nativecoroutines.ksp.KmpNativeCoroutinesSymbolProcessorProvider -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | version: 1.0 2 | projectJDK: '17' 3 | profile: 4 | name: qodana.recommended 5 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/xcode,cocoapods 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode,cocoapods 3 | 4 | ### CocoaPods ### 5 | ## CocoaPods GitIgnore Template 6 | 7 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 8 | # - Also handy if you have a large number of dependant pods 9 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 10 | Pods/ 11 | 12 | ### Xcode ### 13 | # Xcode 14 | # 15 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 16 | 17 | ## User settings 18 | xcuserdata/ 19 | 20 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 21 | *.xcscmblueprint 22 | *.xccheckout 23 | 24 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 25 | build/ 26 | DerivedData/ 27 | *.moved-aside 28 | *.pbxuser 29 | !default.pbxuser 30 | *.mode1v3 31 | !default.mode1v3 32 | *.mode2v3 33 | !default.mode2v3 34 | *.perspectivev3 35 | !default.perspectivev3 36 | 37 | ## Gcc Patch 38 | /*.gcno 39 | 40 | ### Xcode Patch ### 41 | *.xcodeproj/* 42 | !*.xcodeproj/project.pbxproj 43 | !*.xcodeproj/xcshareddata/ 44 | !*.xcworkspace/contents.xcworkspacedata 45 | **/xcshareddata/WorkspaceSettings.xcsettings 46 | 47 | # End of https://www.toptal.com/developers/gitignore/api/xcode,cocoapods -------------------------------------------------------------------------------- /sample/Async/AsyncTestUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTestUtils.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 19/11/2021. 6 | // 7 | 8 | import XCTest 9 | import NativeCoroutinesSampleShared 10 | 11 | func assertJobCompleted(_ integrationTests: IntegrationTests) async { 12 | await withCheckedContinuation { (continuation: CheckedContinuation) in 13 | // The job should complete soon after the stream finishes 14 | DispatchQueue.global().asyncAfter(deadline: .now() + 1) { 15 | XCTAssertEqual(integrationTests.uncompletedJobCount, 0, "The job should have completed by now") 16 | continuation.resume() 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/Async/RandomLettersAsyncViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomLettersAsyncViewModel.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 13/06/2021. 6 | // 7 | 8 | import Foundation 9 | import KMPNativeCoroutinesAsync 10 | import NativeCoroutinesSampleShared 11 | 12 | /// `RandomLettersViewModel` implementation that uses Swifts Async/Await. 13 | class RandomLettersAsyncViewModel: RandomLettersViewModel { 14 | 15 | @Published private(set) var result: Result? = nil 16 | @Published private(set) var isLoading: Bool = false 17 | 18 | private let randomLettersGenerator = RandomLettersGenerator() 19 | 20 | func loadRandomLetters(throwException: Bool) { 21 | Task { 22 | isLoading = true 23 | result = nil 24 | do { 25 | let letters = try await asyncFunction(for: randomLettersGenerator.getRandomLetters(throwException: throwException)) 26 | result = .success(letters) 27 | } catch { 28 | result = .failure(error) 29 | } 30 | isLoading = false 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/Async/SwiftUIAsyncTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIAsyncTest.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 15/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | import NativeCoroutinesSampleShared 10 | import KMPNativeCoroutinesAsync 11 | 12 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) 13 | struct SwiftUIAsyncTest: View { 14 | 15 | var tests: SuspendIntegrationTests 16 | 17 | var body: some View { 18 | List { 19 | 20 | }.refreshable { 21 | print("Refreshable started") 22 | do { 23 | let result = try await asyncFunction(for: tests.returnValue(value: 20, delay: 10000)) 24 | print("Refreshable result: \(result)") 25 | } catch { 26 | print("Refreshable error: \(error)") 27 | } 28 | }.task { 29 | print("Task started") 30 | do { 31 | let result = try await asyncFunction(for: tests.returnValue(value: 2, delay: 10000)) 32 | print("Task result: \(result)") 33 | } catch { 34 | print("Task error: \(error)") 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /sample/Combine/RandomLettersCombineViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomWordCombineViewModel.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 09/06/2021. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | import KMPNativeCoroutinesCombine 11 | import NativeCoroutinesSampleShared 12 | 13 | /// `RandomLettersViewModel` implementation that uses Combine. 14 | class RandomLettersCombineViewModel: RandomLettersViewModel { 15 | 16 | @Published private(set) var result: Result? = nil 17 | @Published private(set) var isLoading: Bool = false 18 | 19 | private let randomLettersGenerator = RandomLettersGenerator() 20 | private var cancellables = Set() 21 | 22 | func loadRandomLetters(throwException: Bool) { 23 | isLoading = true 24 | result = nil 25 | createFuture(for: randomLettersGenerator.getRandomLetters(throwException: throwException)) 26 | // Update the UI on the main thread 27 | .receive(on: DispatchQueue.main) 28 | .sink { [weak self] completion in 29 | if case let .failure(error) = completion { 30 | self?.result = .failure(error) 31 | } 32 | self?.isLoading = false 33 | } receiveValue: { [weak self] word in 34 | self?.result = .success(word) 35 | }.store(in: &cancellables) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample/IntegrationTests/NewMemoryModelIntegrationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewMemoryModelIntegrationTests.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 06/11/2021. 6 | // 7 | 8 | import XCTest 9 | import KMPNativeCoroutinesCore 10 | import NativeCoroutinesSampleShared 11 | 12 | class NewMemoryModelIntegrationTests: XCTestCase { 13 | 14 | private typealias IntegrationTests = NativeCoroutinesSampleShared.NewMemoryModelIntegrationTests 15 | 16 | func testReturnMutableData() { 17 | let integrationTests = IntegrationTests() 18 | let valueExpectation = expectation(description: "Waiting for value") 19 | _ = integrationTests.generateRandomMutableData()({ value, unit in 20 | let dataFromBackground = value.dataFromBackground 21 | let dataFromMain = value.dataFromMain 22 | XCTAssertNotNil(dataFromBackground, "Data from background should be set") 23 | XCTAssertNotNil(dataFromMain, "Data from main should be set") 24 | XCTAssertNotEqual(dataFromBackground, dataFromMain, "Data from background and main should be different") 25 | value.dataFromBackground = dataFromMain 26 | value.dataFromMain = dataFromBackground 27 | XCTAssertEqual(value.dataFromBackground, dataFromMain, "Data from background should now be data from main") 28 | XCTAssertEqual(value.dataFromMain, dataFromBackground, "Data from main should now be data from background") 29 | valueExpectation.fulfill() 30 | return unit 31 | }, { _, unit in unit }, { _, unit in unit }) 32 | wait(for: [valueExpectation], timeout: 1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample/IntegrationTests/TestUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestUtils.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 14/06/2021. 6 | // 7 | 8 | import XCTest 9 | 10 | extension XCTestCase { 11 | 12 | func randomInt(min: Int32 = .min, max: Int32 = .max) -> Int32 { 13 | Int32.random(in: min...max) 14 | } 15 | 16 | func randomString() -> String { 17 | (1...10).map { _ in String(Unicode.Scalar(UInt8.random(in: 65...90))) }.joined() 18 | } 19 | 20 | func delay(_ timeInterval: TimeInterval) { 21 | let delayExpectation = XCTestExpectation() 22 | delayExpectation.isInverted = true 23 | wait(for: [delayExpectation], timeout: timeInterval) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/Issues/GH206Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GH206.swift 3 | // KMPNativeCoroutinesSample 4 | // 5 | // Created by Rick Clephas on 04/03/2025. 6 | // 7 | 8 | import XCTest 9 | import KMPNativeCoroutinesCore 10 | import NativeCoroutinesSampleShared 11 | 12 | class GH206Tests: XCTestCase { 13 | 14 | func testReturnFlowValue() { 15 | let valueExpectation = expectation(description: "Waiting for value") 16 | _ = GH206().property({ value, next, _ in 17 | XCTAssertEqual(value, "GH206", "Received incorrect value") 18 | valueExpectation.fulfill() 19 | return next() 20 | }, { _, unit in unit }, { _, unit in unit }) 21 | wait(for: [valueExpectation], timeout: 2) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/Issues/GH219Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GH219Tests.swift 3 | // KMPNativeCoroutinesSample 4 | // 5 | // Created by Rick Clephas on 04/06/2025. 6 | // 7 | 8 | import XCTest 9 | import NativeCoroutinesSampleShared 10 | 11 | class GH219Tests: XCTestCase { 12 | 13 | func testReturnStateFlowValue() { 14 | let gh219 = GH219Kt.createGH219() 15 | #if NATIVE_COROUTINES_KSP_MODE 16 | XCTAssertEqual(GH219NativeKt.state(gh219), "GH219") 17 | #else 18 | XCTAssertEqual(gh219.state, "GH219") 19 | #endif 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/RxSwift/RandomLettersRxSwiftViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomLettersRxSwiftViewModel.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 09/07/2021. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import KMPNativeCoroutinesRxSwift 11 | import NativeCoroutinesSampleShared 12 | 13 | /// `RandomLettersViewModel` implementation that uses RxSwift. 14 | class RandomLettersRxSwiftViewModel: RandomLettersViewModel { 15 | 16 | @Published private(set) var result: Result? = nil 17 | @Published private(set) var isLoading: Bool = false 18 | 19 | private let randomLettersGenerator = RandomLettersGenerator() 20 | 21 | func loadRandomLetters(throwException: Bool) { 22 | isLoading = true 23 | result = nil 24 | _ = createSingle(for: randomLettersGenerator.getRandomLetters(throwException: throwException)) 25 | // Update the UI on the main thread 26 | .observe(on: MainScheduler.instance) 27 | .subscribe(onSuccess: { [weak self] word in 28 | self?.result = .success(word) 29 | }, onFailure: { [weak self] error in 30 | self?.result = .failure(error) 31 | }, onDisposed: { [weak self] in 32 | self?.isLoading = false 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/UI/ClockView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockView.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 07/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ClockView: View { 11 | 12 | @ObservedObject var viewModel: ViewModel 13 | 14 | private var isMonitoringBinding: Binding { 15 | Binding { viewModel.isMonitoring } set: { isMonitoring in 16 | if !isMonitoring && viewModel.isMonitoring { 17 | viewModel.stopMonitoring() 18 | } else if isMonitoring && !viewModel.isMonitoring { 19 | viewModel.startMonitoring() 20 | } 21 | } 22 | } 23 | 24 | var body: some View { 25 | ScrollView { 26 | VStack{ 27 | Text(viewModel.time) 28 | .font(.system(size: 30, weight: .bold, design: .monospaced)) 29 | Text("Monitor time") 30 | Toggle("", isOn: isMonitoringBinding) 31 | .labelsHidden() 32 | Button("Update time") { 33 | viewModel.updateTime() 34 | }.disabled(viewModel.isMonitoring) 35 | } 36 | }.navigationBarTitle(inlineTitle: "Clock") 37 | } 38 | } 39 | 40 | struct ClockView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | ClockView(viewModel: ClockPreviewViewModel()) 43 | .previewDevice("iPhone 14 Pro") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample/UI/ClockViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockViewModel.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 07/06/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A type of object that is used as the view model for the `ClockView`. 11 | @MainActor 12 | protocol ClockViewModel: ObservableObject { 13 | /// The current time formatted as "HH:mm:ss". 14 | var time: String { get } 15 | /// Indicates if the time is currently being monitored. 16 | var isMonitoring: Bool { get } 17 | 18 | /// Start monitoring the time. 19 | func startMonitoring() 20 | /// Stops monitoring the time. 21 | func stopMonitoring() 22 | /// Update the `time` value to the current time. 23 | func updateTime() 24 | } 25 | 26 | /// `ClockViewModel` implementation for the SwiftUI previews. 27 | class ClockPreviewViewModel: ClockViewModel { 28 | let time: String 29 | let isMonitoring: Bool 30 | 31 | init(time: String = "20:08:58", isMonitoring: Bool = false) { 32 | self.time = time 33 | self.isMonitoring = isMonitoring 34 | } 35 | 36 | func startMonitoring() { } 37 | func stopMonitoring() { } 38 | func updateTime() { } 39 | } 40 | -------------------------------------------------------------------------------- /sample/UI/NavigationBarTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBarTitle.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 09/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | #if os(iOS) 11 | extension View { 12 | func navigationBarTitle(inlineTitle title: String) -> some View { 13 | return navigationBarTitle(Text(title), displayMode: .inline) 14 | } 15 | } 16 | #else 17 | extension View { 18 | func navigationBarTitle(inlineTitle title: String) -> some View { 19 | return self 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /sample/UI/RandomLettersView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomWordView.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 09/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RandomLettersView: View { 11 | 12 | @ObservedObject var viewModel: ViewModel 13 | 14 | var body: some View { 15 | ScrollView { 16 | VStack { 17 | Group { 18 | if viewModel.isLoading { 19 | Text("Loading..\n") 20 | } else if case let .success(word) = viewModel.result { 21 | Text("Letters:\n\(word)") 22 | } else if case let .failure(error) = viewModel.result { 23 | Text("Error:\n\(error.localizedDescription)") 24 | } 25 | } 26 | .font(.system(size: 25, weight: .bold)) 27 | .multilineTextAlignment(.center) 28 | Button("Random letters") { 29 | viewModel.loadRandomLetters(throwException: false) 30 | }.disabled(viewModel.isLoading) 31 | Button("Throw exception") { 32 | viewModel.loadRandomLetters(throwException: true) 33 | }.disabled(viewModel.isLoading) 34 | } 35 | }.navigationBarTitle(inlineTitle: "Random letters") 36 | } 37 | } 38 | 39 | struct RandomLettersView_Previews: PreviewProvider { 40 | static var previews: some View { 41 | RandomLettersView(viewModel: RandomLettersPreviewViewModel(isLoading: true)) 42 | .previewDevice("iPhone 14 Pro") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sample/UI/RandomLettersViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomWordViewModel.swift 3 | // Sample 4 | // 5 | // Created by Rick Clephas on 09/06/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A type of object that is used as the view model for the `RandomLettersView`. 11 | @MainActor 12 | protocol RandomLettersViewModel: ObservableObject { 13 | /// The result of the `loadRandomLetters` action. 14 | var result: Result? { get } 15 | /// Indicates if random letters are being loaded. 16 | var isLoading: Bool { get } 17 | 18 | /// Loads random letters or throws an exception 19 | func loadRandomLetters(throwException: Bool) 20 | } 21 | 22 | /// `RandomLettersViewModel` implementation for the SwiftUI previews. 23 | class RandomLettersPreviewViewModel: RandomLettersViewModel { 24 | let result: Result? 25 | var isLoading: Bool 26 | 27 | init(result: Result? = nil, isLoading: Bool = false) { 28 | self.result = result 29 | self.isLoading = isLoading 30 | } 31 | 32 | func loadRandomLetters(throwException: Bool) { } 33 | } 34 | -------------------------------------------------------------------------------- /sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | allprojects { 9 | repositories { 10 | mavenCentral() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx2g 3 | -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickclephas/KMP-NativeCoroutines/26487987d20ef9b5c8972eba72fac01a5200477c/sample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /sample/iOS App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS App 4 | // 5 | // Created by Rick Clephas on 08/06/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /sample/iOS App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/iOS App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/iOS App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /sample/iOS App/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/macOS App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS App 4 | // 5 | // Created by Rick Clephas on 08/06/2021. 6 | // 7 | 8 | import Cocoa 9 | import SwiftUI 10 | 11 | @main 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | var window: NSWindow! 15 | 16 | 17 | func applicationDidFinishLaunching(_ aNotification: Notification) { 18 | // Create the SwiftUI view that provides the window contents. 19 | let contentView = NavigationView { RootView() } 20 | 21 | // Create the window and set the content view. 22 | window = NSWindow( 23 | contentRect: NSRect(x: 0, y: 0, width: 800, height: 300), 24 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 25 | backing: .buffered, defer: false) 26 | window.isReleasedWhenClosed = false 27 | window.center() 28 | window.setFrameAutosaveName("Main Window") 29 | window.contentView = NSHostingView(rootView: contentView) 30 | window.makeKeyAndOrderFront(nil) 31 | } 32 | 33 | func applicationWillTerminate(_ aNotification: Notification) { 34 | // Insert code here to tear down your application 35 | } 36 | 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /sample/macOS App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/macOS App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/macOS App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/macOS App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /sample/macOS App/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/macOS App/macOS_App.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("..") 3 | repositories { 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | rootProject.name = "sample" 10 | 11 | include(":shared") 12 | 13 | includeBuild("..") { 14 | dependencySubstitution { 15 | listOf("annotations", "compiler", "compiler-embeddable", "core", "ksp").forEach { 16 | substitute(module("com.rickclephas.kmp:kmp-nativecoroutines-$it")) 17 | .using(project(":kmp-nativecoroutines-$it")) 18 | } 19 | } 20 | } 21 | 22 | dependencyResolutionManagement { 23 | versionCatalogs { 24 | create("libs") { 25 | from(files("../gradle/libs.versions.toml")) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) 3 | alias(libs.plugins.kotlin.plugin.serialization) 4 | if (System.getenv("NATIVE_COROUTINES_KSP_MODE")?.toBooleanStrictOrNull() == true) { 5 | alias(libs.plugins.ksp) 6 | } 7 | id("com.rickclephas.kmp.nativecoroutines") 8 | } 9 | 10 | version = "1.0" 11 | 12 | kotlin { 13 | jvmToolchain(11) 14 | 15 | explicitApi() 16 | val macosX64 = macosX64() 17 | val macosArm64 = macosArm64() 18 | val iosArm64 = iosArm64() 19 | val iosX64 = iosX64() 20 | val iosSimulatorArm64 = iosSimulatorArm64() 21 | val watchosArm32 = watchosArm32() 22 | val watchosArm64 = watchosArm64() 23 | val watchosX64 = watchosX64() 24 | val watchosSimulatorArm64 = watchosSimulatorArm64() 25 | val tvosArm64 = tvosArm64() 26 | val tvosX64 = tvosX64() 27 | val tvosSimulatorArm64 = tvosSimulatorArm64() 28 | sourceSets { 29 | all { 30 | languageSettings.optIn("kotlin.experimental.ExperimentalObjCName") 31 | } 32 | commonMain { 33 | dependencies { 34 | implementation(libs.kotlinx.serialization.json) 35 | } 36 | } 37 | commonTest { 38 | dependencies { 39 | implementation(libs.kotlin.test) 40 | } 41 | } 42 | listOf( 43 | macosX64, macosArm64, 44 | iosArm64, iosX64, iosSimulatorArm64, 45 | watchosArm32, watchosArm64, watchosX64, watchosSimulatorArm64, 46 | tvosArm64, tvosX64, tvosSimulatorArm64 47 | ).forEach { 48 | it.binaries.framework { 49 | baseName = "NativeCoroutinesSampleShared" 50 | } 51 | } 52 | } 53 | } 54 | 55 | nativeCoroutines { 56 | k2Mode = System.getenv("NATIVE_COROUTINES_KSP_MODE")?.toBooleanStrictOrNull() != true 57 | } 58 | -------------------------------------------------------------------------------- /sample/shared/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/ClockApple.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample 2 | 3 | import platform.Foundation.NSDate 4 | import platform.Foundation.date 5 | import platform.Foundation.timeIntervalSince1970 6 | 7 | internal actual fun epochSeconds(): Long = NSDate.date().timeIntervalSince1970().toLong() 8 | -------------------------------------------------------------------------------- /sample/shared/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/issues/GH206.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.issues 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.flowOf 6 | 7 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 8 | public actual class GH206 { 9 | @NativeCoroutines 10 | public actual val property: Flow = flowOf("GH206") 11 | } 12 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/Clock.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.DelicateCoroutinesApi 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import kotlinx.coroutines.flow.StateFlow 9 | import kotlinx.coroutines.flow.asStateFlow 10 | import kotlinx.coroutines.launch 11 | import kotlin.time.Duration.Companion.seconds 12 | 13 | @OptIn(DelicateCoroutinesApi::class) 14 | public object Clock { 15 | 16 | private val _time = MutableStateFlow(0L) 17 | @NativeCoroutines 18 | public val time: StateFlow = _time.asStateFlow() 19 | 20 | init { 21 | GlobalScope.launch { 22 | while (true) { 23 | _time.value = epochSeconds() 24 | delay(1.seconds) 25 | } 26 | } 27 | } 28 | } 29 | 30 | internal expect fun epochSeconds(): Long 31 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/RandomLettersGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.delay 5 | import kotlin.random.Random 6 | import kotlin.time.Duration.Companion.seconds 7 | 8 | public object RandomLettersGenerator { 9 | 10 | @NativeCoroutines 11 | public suspend fun getRandomLetters(throwException: Boolean): String { 12 | delay(2.seconds) 13 | if (throwException) { 14 | throw RuntimeException("the best exception ever") 15 | } 16 | val chars = mutableListOf() 17 | repeat(5) { 18 | chars.add(Random.nextInt(65, 91).toChar()) 19 | } 20 | return chars.joinToString("") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/issues/GH206.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.issues 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 7 | public expect class GH206 { 8 | @NativeCoroutines 9 | public val property: Flow 10 | } 11 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/issues/GH219.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.issues 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState 4 | import kotlinx.coroutines.flow.MutableStateFlow 5 | import kotlinx.coroutines.flow.StateFlow 6 | 7 | public interface GH219 { 8 | @NativeCoroutinesState 9 | public val state: StateFlow 10 | } 11 | 12 | internal class GH219Impl: GH219 { 13 | override val state: StateFlow = MutableStateFlow("GH219") 14 | } 15 | 16 | public fun createGH219(): GH219 = GH219Impl() 17 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/tests/CompilerIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.tests 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesIgnore 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flow 7 | 8 | @Suppress("RedundantSuspendModifier") 9 | public class CompilerIntegrationTests: IntegrationTests() { 10 | 11 | @NativeCoroutines 12 | public suspend fun returnGenericClassValue(value: V): V { 13 | return value 14 | } 15 | 16 | @NativeCoroutines 17 | public suspend fun returnDefaultValue(value: Int = 1): Int { 18 | return value 19 | } 20 | 21 | @NativeCoroutines 22 | public suspend fun returnGenericValue(value: T): T { 23 | return value 24 | } 25 | 26 | public fun returnAppendable(value: String): Appendable = StringBuilder(value) 27 | 28 | @NativeCoroutines 29 | public suspend fun returnConstrainedGenericValue(value: T): T { 30 | return value 31 | } 32 | 33 | @NativeCoroutines 34 | public suspend fun returnGenericValues(values: List): List { 35 | return values 36 | } 37 | 38 | @NativeCoroutines 39 | public suspend fun returnGenericVarargValues(vararg values: T): Array { 40 | return values 41 | } 42 | 43 | @NativeCoroutines 44 | @Suppress("UnusedReceiverParameter") 45 | public suspend fun List.returnGenericValueFromExtension(value: T): T { 46 | return value 47 | } 48 | 49 | @NativeCoroutines 50 | public fun returnGenericFlow(value: T): Flow = flow { 51 | emit(value) 52 | } 53 | 54 | @NativeCoroutinesIgnore 55 | public suspend fun returnIgnoredValue(value: Int): Int { 56 | return value 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/tests/IntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.tests 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.SupervisorJob 7 | 8 | public abstract class IntegrationTests { 9 | 10 | private val job = SupervisorJob() 11 | @NativeCoroutineScope 12 | internal open val coroutineScope = CoroutineScope(job + Dispatchers.Default) 13 | 14 | public val activeJobCount: Int 15 | get() = job.children.count { it.isActive } 16 | 17 | public val uncompletedJobCount: Int 18 | get() = job.children.count { !it.isCompleted } 19 | } 20 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/tests/NewMemoryModelIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.tests 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import kotlin.random.Random 7 | 8 | public class NewMemoryModelIntegrationTests: IntegrationTests() { 9 | 10 | public class MutableData { 11 | public var dataFromBackground: String? = null 12 | public var dataFromMain: String? = null 13 | } 14 | 15 | private fun randomString(): String { 16 | val chars = mutableListOf() 17 | repeat(5) { 18 | chars.add(Random.nextInt(65, 91).toChar()) 19 | } 20 | return chars.joinToString("") 21 | } 22 | 23 | @NativeCoroutines 24 | public suspend fun generateRandomMutableData(): MutableData { 25 | val data = MutableData() 26 | withContext(Dispatchers.Main) { 27 | data.dataFromMain = randomString() 28 | withContext(Dispatchers.Default) { 29 | data.dataFromBackground = randomString() 30 | } 31 | } 32 | return data 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/tests/SuspendIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.tests 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flow 7 | 8 | public class SuspendIntegrationTests: IntegrationTests() { 9 | 10 | @NativeCoroutines 11 | public suspend fun returnValue(value: Int, delay: Long): Int { 12 | delay(delay) 13 | return value 14 | } 15 | 16 | @NativeCoroutines 17 | public suspend fun returnNull(delay: Long): Int? { 18 | delay(delay) 19 | return null 20 | } 21 | 22 | @NativeCoroutines 23 | public suspend fun throwException(message: String, delay: Long): Int { 24 | delay(delay) 25 | throw Exception(message) 26 | } 27 | 28 | @NativeCoroutines 29 | public suspend fun throwError(message: String, delay: Long): Int { 30 | delay(delay) 31 | throw Error(message) 32 | } 33 | 34 | @NativeCoroutines 35 | public suspend fun returnFromCallback(delay: Long, callback: () -> Int): Int { 36 | delay(delay) 37 | return callback() 38 | } 39 | 40 | @NativeCoroutines 41 | public suspend fun getFlow(count: Int, delay: Long): Flow { 42 | delay(delay) 43 | return flow { 44 | repeat(count) { 45 | delay(delay) 46 | emit(it) 47 | } 48 | } 49 | } 50 | 51 | @NativeCoroutines 52 | public suspend fun returnUnit(delay: Long) { 53 | delay(delay) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/rickclephas/kmp/nativecoroutines/sample/tests/ThreadLockIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package com.rickclephas.kmp.nativecoroutines.sample.tests 2 | 3 | import com.rickclephas.kmp.nativecoroutines.NativeCoroutines 4 | import kotlinx.coroutines.* 5 | import kotlinx.coroutines.flow.* 6 | import kotlin.time.Duration.Companion.seconds 7 | 8 | @OptIn(DelicateCoroutinesApi::class) 9 | public class ThreadLockIntegrationTests: IntegrationTests() { 10 | 11 | override val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) 12 | 13 | private val _stateFlow = MutableStateFlow(0) 14 | @NativeCoroutines 15 | public val stateFlow: StateFlow = _stateFlow.asStateFlow() 16 | 17 | init { 18 | GlobalScope.launch { 19 | while(true) { 20 | delay(1.seconds) 21 | _stateFlow.update { it + 1 } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "App Icon - App Store.imagestack", 5 | "idiom" : "tv", 6 | "role" : "primary-app-icon", 7 | "size" : "1280x768" 8 | }, 9 | { 10 | "filename" : "App Icon.imagestack", 11 | "idiom" : "tv", 12 | "role" : "primary-app-icon", 13 | "size" : "400x240" 14 | }, 15 | { 16 | "filename" : "Top Shelf Image Wide.imageset", 17 | "idiom" : "tv", 18 | "role" : "top-shelf-image-wide", 19 | "size" : "2320x720" 20 | }, 21 | { 22 | "filename" : "Top Shelf Image.imageset", 23 | "idiom" : "tv", 24 | "role" : "top-shelf-image", 25 | "size" : "1920x720" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "author" : "xcode", 22 | "version" : 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "author" : "xcode", 22 | "version" : 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/tvOS App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/tvOS App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /sample/tvOS App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /sample/tvOS App/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit App/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | watchOS App WatchKit App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | UIInterfaceOrientationPortraitUpsideDown 27 | 28 | WKWatchKitApp 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "Circular.imageset", 5 | "idiom" : "watch", 6 | "role" : "circular" 7 | }, 8 | { 9 | "filename" : "Extra Large.imageset", 10 | "idiom" : "watch", 11 | "role" : "extra-large" 12 | }, 13 | { 14 | "filename" : "Graphic Bezel.imageset", 15 | "idiom" : "watch", 16 | "role" : "graphic-bezel" 17 | }, 18 | { 19 | "filename" : "Graphic Circular.imageset", 20 | "idiom" : "watch", 21 | "role" : "graphic-circular" 22 | }, 23 | { 24 | "filename" : "Graphic Corner.imageset", 25 | "idiom" : "watch", 26 | "role" : "graphic-corner" 27 | }, 28 | { 29 | "filename" : "Graphic Extra Large.imageset", 30 | "idiom" : "watch", 31 | "role" : "graphic-extra-large" 32 | }, 33 | { 34 | "filename" : "Graphic Large Rectangular.imageset", 35 | "idiom" : "watch", 36 | "role" : "graphic-large-rectangular" 37 | }, 38 | { 39 | "filename" : "Modular.imageset", 40 | "idiom" : "watch", 41 | "role" : "modular" 42 | }, 43 | { 44 | "filename" : "Utilitarian.imageset", 45 | "idiom" : "watch", 46 | "role" : "utilitarian" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : ">161" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">183" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : ">161" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">183" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : ">161" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">183" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : ">161" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">183" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/HostingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostingController.swift 3 | // watchOS App WatchKit Extension 4 | // 5 | // Created by Rick Clephas on 05/09/2021. 6 | // 7 | 8 | import WatchKit 9 | import Foundation 10 | import SwiftUI 11 | 12 | class HostingController: WKHostingController { 13 | override var body: RootView { 14 | return RootView() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | watchOS App WatchKit Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | CLKComplicationPrincipalClass 24 | $(PRODUCT_MODULE_NAME).ComplicationController 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | com.rickclephas.kmp.nativecoroutines.sample.watchkitapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | WKExtensionDelegateClassName 36 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 37 | WKWatchOnly 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sample/watchOS App WatchKit Extension/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | pluginManagement { 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | } 9 | 10 | dependencyResolutionManagement { 11 | repositories { 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "kmp-nativecoroutines" 17 | 18 | include(":kmp-nativecoroutines-core") 19 | include(":kmp-nativecoroutines-annotations") 20 | include(":kmp-nativecoroutines-compiler") 21 | include(":kmp-nativecoroutines-compiler-embeddable") 22 | include(":kmp-nativecoroutines-gradle-plugin") 23 | include(":kmp-nativecoroutines-idea-plugin") 24 | include(":kmp-nativecoroutines-ksp") 25 | --------------------------------------------------------------------------------