From 7765534c56c7a2d00926730ffc1c59721a945ad0 Mon Sep 17 00:00:00 2001 From: epicadk Date: Sat, 24 Jun 2023 16:27:14 +0530 Subject: [PATCH] [WIP] feat: @ZiplineId --- .../zipline/api/fir/FirZiplineApiReader.kt | 23 ++++++++++++++++--- .../main/kotlin/app/cash/zipline/kotlin/ir.kt | 20 +++++++++++++++- .../app/cash/zipline/ZiplineFunction.kt | 2 +- .../kotlin/app/cash/zipline/ZiplineId.kt | 22 ++++++++++++++++++ .../app/cash/zipline/NewServicesTest.kt | 15 ++++++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineId.kt diff --git a/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/api/fir/FirZiplineApiReader.kt b/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/api/fir/FirZiplineApiReader.kt index 46ee728f96..f64e9fc682 100644 --- a/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/api/fir/FirZiplineApiReader.kt +++ b/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/api/fir/FirZiplineApiReader.kt @@ -19,6 +19,7 @@ import app.cash.zipline.kotlin.BridgedInterface.Companion.NON_INTERFACE_FUNCTION import app.cash.zipline.kotlin.FqPackageName import app.cash.zipline.kotlin.classId import java.io.File +import org.jetbrains.kotlin.fir.FirAnnotationContainer import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol @@ -26,8 +27,10 @@ import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirFunction import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId import org.jetbrains.kotlin.fir.declarations.utils.isInterface import org.jetbrains.kotlin.fir.declarations.utils.isSuspend +import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.pipeline.FirResult import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol @@ -39,6 +42,8 @@ import org.jetbrains.kotlin.fir.types.FirTypeProjectionWithVariance import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.types.FirUserTypeRef import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction +import org.jetbrains.kotlin.utils.addToStdlib.cast fun readFirZiplineApi( sources: Collection, @@ -52,6 +57,7 @@ fun readFirZiplineApi( private val ziplineFqPackage = FqPackageName("app.cash.zipline") private val ziplineServiceClassId = ziplineFqPackage.classId("ZiplineService") +private val ziplineIdClassId = ziplineFqPackage.classId("ZiplineId") /** * Read the frontend intermediate representation of a program and emit its ZiplineService @@ -128,8 +134,9 @@ internal class FirZiplineApiReader( append(valueParameters.joinToString { it.returnTypeRef.asString() }) append("): ${returnTypeRef.asString()}") } - - return FirZiplineFunction(signature) + return getZiplineId()?.let { + FirZiplineFunction(it, signature) + } ?: FirZiplineFunction(signature) } private fun FirProperty.asDeclaredZiplineFunction(): FirZiplineFunction { @@ -137,7 +144,9 @@ internal class FirZiplineApiReader( isVar -> "var ${symbol.name.identifier}: ${returnTypeRef.asString()}" else -> "val ${symbol.name.identifier}: ${returnTypeRef.asString()}" } - return FirZiplineFunction(signature) + return getZiplineId()?.let { + FirZiplineFunction(it,signature) + } ?: FirZiplineFunction(signature) } /** See [app.cash.zipline.kotlin.asString]. */ @@ -177,6 +186,14 @@ internal class FirZiplineApiReader( } } + @OptIn(UnsafeCastFunction::class) + private fun FirAnnotationContainer.getZiplineId(): String? { + return getAnnotationByClassId( + ziplineIdClassId, + session + )?.cast>()?.value + } + private fun List.findRegularClassesRecursive(): List { val classes = filterIsInstance() return classes + classes.flatMap { it.declarations.findRegularClassesRecursive() } diff --git a/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/kotlin/ir.kt b/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/kotlin/ir.kt index 7868901402..7f7fae7b32 100644 --- a/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/kotlin/ir.kt +++ b/zipline-kotlin-plugin/src/main/kotlin/app/cash/zipline/kotlin/ir.kt @@ -46,6 +46,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpressionBody @@ -72,8 +73,11 @@ import org.jetbrains.kotlin.ir.util.constructors import org.jetbrains.kotlin.ir.util.createDispatchReceiverParameter import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.ir.util.getAnnotation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction +import org.jetbrains.kotlin.utils.addToStdlib.cast /** Returns a string as specified by ZiplineFunction.signature. */ internal val IrSimpleFunction.signature: String @@ -102,9 +106,23 @@ internal val IrSimpleFunction.signature: String } } +@UnsafeCastFunction +private fun IrElement.getConstValue(): T { + return cast>().value +} + +private val ziplineFqPackageName = FqPackageName("app.cash.zipline") +private val ziplineIdAnnotationFqName = ziplineFqPackageName.classId("ZiplineId").asSingleFqName() + + /** Returns a string as specified by ZiplineFunction.id. */ +@OptIn(UnsafeCastFunction::class) internal val IrSimpleFunction.id: String - get() = signature.signatureHash() + get() { + + return getAnnotation(ziplineIdAnnotationFqName)?.getValueArgument(0) + ?.getConstValue() ?: signature.signatureHash() + } /** Thrown on invalid or unexpected input code. */ class ZiplineCompilationException( diff --git a/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineFunction.kt b/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineFunction.kt index bbf39f7144..a4996b5c21 100644 --- a/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineFunction.kt +++ b/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineFunction.kt @@ -21,7 +21,7 @@ package app.cash.zipline interface ZiplineFunction { /** * A unique id for this function. By default this is the first 6 bytes of the SHA-256 of the - * function's signature, base64-encoded. + * function's signature, base64-encoded. To provide your own id, annotate your function with [ZiplineId]. * * These are sample values that correspond to the sample values in [signature]. * diff --git a/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineId.kt b/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineId.kt new file mode 100644 index 0000000000..013e751017 --- /dev/null +++ b/zipline/src/commonMain/kotlin/app/cash/zipline/ZiplineId.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Cash App + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.zipline + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +// TODO document usage +annotation class ZiplineId(val id: String) diff --git a/zipline/src/jniTest/kotlin/app/cash/zipline/NewServicesTest.kt b/zipline/src/jniTest/kotlin/app/cash/zipline/NewServicesTest.kt index da7e7a9da9..e10111f241 100644 --- a/zipline/src/jniTest/kotlin/app/cash/zipline/NewServicesTest.kt +++ b/zipline/src/jniTest/kotlin/app/cash/zipline/NewServicesTest.kt @@ -411,6 +411,18 @@ class NewSerializersTest { suspend fun echo(): @Contextual RequiresContextual } + @Test + fun ziplineIdIsAppliedToGeneratedService() { + val function = + onlyZiplineFunction(serviceSerializer = ziplineServiceSerializer()) as ZiplineFunction<*> + + assertEquals(function.id, testZiplineId) + } + interface SimpleZiplineService : ZiplineService { + @ZiplineId(testZiplineId) + fun annotatedFunction() + } + private fun suspendCallbackSerializer( resultSerializer: KSerializer, ): KSerializer> { @@ -475,4 +487,7 @@ class NewSerializersTest { json.decodeFromString(actual, json.encodeToString(expected, sampleValue)), ) } + companion object { + private const val testZiplineId = "TEST_ZIPLINE_ID" + } }