Skip to content

Commit 4081de9

Browse files
committed
Grpc.Method Annotation (#487)
1 parent 5885876 commit 4081de9

File tree

18 files changed

+615
-143
lines changed

18 files changed

+615
-143
lines changed

cinterop-c/build_target.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ CONFIG=release
2626
mkdir -p "$(dirname "$DST")"
2727

2828
echo "==> Building $LABEL to $DST" >&2
29-
KONAN_DEP="--define=KONAN_DEPS=/Users/johannes.zottele/.konan//dependencies"
30-
bazel build "$LABEL" --config="$KONAN_TARGET" --config="$CONFIG" $KONAN_DEP "--define=KONAN_HOME=$KONAN_HOME" >/dev/null
29+
KONAN_DEP="--define=KONAN_DEPS=$HOME/.konan/dependencies"
30+
bazel build "$LABEL" --config="$KONAN_TARGET" --config="$CONFIG" "$KONAN_DEP" "--define=KONAN_HOME=$KONAN_HOME"
3131

3232
# Ask Bazel what file(s) this target produced under this platform
33-
out="$(bazel cquery "$LABEL" --config="$KONAN_TARGET" --config="$CONFIG" $KONAN_DEP "--define=KONAN_HOME=$KONAN_HOME" --output=files | head -n1)"
33+
out="$(bazel cquery "$LABEL" --config="$KONAN_TARGET" --config="$CONFIG" "$KONAN_DEP" "--define=KONAN_HOME=$KONAN_HOME" --output=files | head -n1)"
3434
[[ -n "$out" ]] || { echo "No output for $LABEL ($SHORT)"; exit 1; }
3535

3636
cp -f "$out" "$DST"

compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcStubGenerator.kt

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,12 +1084,12 @@ internal class RpcStubGenerator(
10841084
* Where:
10851085
* - `<callable-name-k>` - the name of the k-th callable in the service
10861086
*/
1087-
private fun irMethodDescriptorMap(resolver: IrValueParameter): IrCallImpl {
1087+
private fun IrBlockBodyBuilder.irMethodDescriptorMap(resolver: IrValueParameter): IrCallImpl {
10881088
return irMapOf(
10891089
keyType = ctx.irBuiltIns.stringType,
10901090
valueType = ctx.grpcPlatformMethodDescriptor.starProjectedType,
10911091
declaration.methods.memoryOptimizedMap { callable ->
1092-
stringConst(callable.name) to irMethodDescriptor(callable, resolver)
1092+
stringConst(callable.grpcName) to irMethodDescriptor(callable, resolver)
10931093
},
10941094
)
10951095
}
@@ -1143,15 +1143,14 @@ internal class RpcStubGenerator(
11431143
* // In scope: resolver: MessageCodecResolver
11441144
*
11451145
* methodDescriptor<<request-type>, <response-type>>(
1146-
* fullMethodName = "${descriptor.serviceFqName}/${callable.name}",
1146+
* fullMethodName = "${descriptor.serviceFqName}/${<from Grpc.Method annotation> ?: callable.name}",
11471147
* requestCodec = <request-codec>,
11481148
* responseCodec = <response-codec>,
11491149
* type = MethodType.<method-type>,
11501150
* schemaDescriptor = null, // null for now
1151-
* // todo understand these values
1152-
* idempotent = true,
1153-
* safe = true,
1154-
* sampledToLocalTracing = true,
1151+
* idempotent = <from Grpc.Method annotation>,
1152+
* safe = <from Grpc.Method annotation>,
1153+
* sampledToLocalTracing = <from Grpc.Method annotation>,
11551154
* )
11561155
* ```
11571156
*
@@ -1163,7 +1162,10 @@ internal class RpcStubGenerator(
11631162
* MethodType.CLIENT_STREAMING, MethodType.BIDI_STREAMING
11641163
* - <request-codec>/<response-codec> - a MessageCodec getter, see [irCodec]
11651164
*/
1166-
private fun irMethodDescriptor(callable: ServiceDeclaration.Callable, resolver: IrValueParameter): IrCall {
1165+
private fun IrBlockBodyBuilder.irMethodDescriptor(
1166+
callable: ServiceDeclaration.Callable,
1167+
resolver: IrValueParameter,
1168+
): IrCall {
11671169
check(callable is ServiceDeclaration.Method) {
11681170
"Only methods are allowed here"
11691171
}
@@ -1181,13 +1183,10 @@ internal class RpcStubGenerator(
11811183
val methodDescriptorType = ctx.grpcPlatformMethodDescriptor.typeWith(requestType, responseType)
11821184

11831185
return vsApi {
1184-
IrCallImplVS(
1185-
startOffset = UNDEFINED_OFFSET,
1186-
endOffset = UNDEFINED_OFFSET,
1186+
irCall(
11871187
type = methodDescriptorType,
1188-
symbol = ctx.functions.methodDescriptor,
1188+
callee = ctx.functions.methodDescriptor,
11891189
typeArgumentsCount = 2,
1190-
valueArgumentsCount = 8,
11911190
)
11921191
}.apply {
11931192
arguments {
@@ -1196,9 +1195,12 @@ internal class RpcStubGenerator(
11961195
+responseType
11971196
}
11981197

1198+
val grpcMethodAnnotation = callable.function
1199+
.getAnnotation(RpcClassId.grpcMethodAnnotation.asSingleFqName())
1200+
11991201
values {
12001202
// fullMethodName
1201-
+stringConst("${declaration.serviceFqName}/${callable.name}")
1203+
+stringConst("${declaration.serviceFqName}/${callable.grpcName}")
12021204

12031205
// requestCodec
12041206
+irCodec(requestType, resolver)
@@ -1233,28 +1235,39 @@ internal class RpcStubGenerator(
12331235
// schemaDescriptor
12341236
+nullConst(ctx.anyNullable)
12351237

1236-
// todo figure out these
1238+
val idempotentArgument = grpcMethodAnnotation
1239+
?.getValueArgument(Name.identifier("idempotent"))
1240+
val idempotent = (idempotentArgument as? IrConst)?.value as? Boolean ?: false
1241+
12371242
// idempotent
1238-
+booleanConst(true)
1243+
+booleanConst(idempotent)
1244+
1245+
val safeArgument = grpcMethodAnnotation
1246+
?.getValueArgument(Name.identifier("safe"))
1247+
val safe = (safeArgument as? IrConst)?.value as? Boolean ?: false
12391248

12401249
// safe
1241-
+booleanConst(true)
1250+
+booleanConst(safe)
1251+
1252+
val sampledToLocalTracingArgument = grpcMethodAnnotation
1253+
?.getValueArgument(Name.identifier("sampledToLocalTracing"))
1254+
val sampledToLocalTracing = (sampledToLocalTracingArgument as? IrConst)?.value as? Boolean ?: true
12421255

12431256
// sampledToLocalTracing
1244-
+booleanConst(true)
1257+
+booleanConst(sampledToLocalTracing)
12451258
}
12461259
}
12471260
}
12481261
}
12491262

12501263
/**
1251-
* If [type] is annotated with [RpcIrContext.withCodecAnnotation],
1264+
* If [messageType] is annotated with [RpcIrContext.withCodecAnnotation],
12521265
* we use its codec object
12531266
*
1254-
* If not, use [resolver].resolve()
1267+
* If not, use [resolver].resolveOrNull()
12551268
*/
1256-
private fun irCodec(type: IrType, resolver: IrValueParameter): IrExpression {
1257-
val owner = type.classOrFail.owner
1269+
private fun IrBlockBodyBuilder.irCodec(messageType: IrType, resolver: IrValueParameter): IrExpression {
1270+
val owner = messageType.classOrFail.owner
12581271
val protobufMessage = owner.getAnnotation(ctx.withCodecAnnotation.owner.kotlinFqName)
12591272

12601273
return if (protobufMessage != null) {
@@ -1270,14 +1283,12 @@ internal class RpcStubGenerator(
12701283
symbol = codec.classOrFail,
12711284
)
12721285
} else {
1273-
vsApi {
1274-
IrCallImplVS(
1275-
startOffset = UNDEFINED_OFFSET,
1276-
endOffset = UNDEFINED_OFFSET,
1277-
type = ctx.grpcMessageCodec.typeWith(type),
1278-
symbol = ctx.functions.grpcMessageCodecResolverResolveOrNull.symbol,
1286+
val codecType = ctx.grpcMessageCodec.typeWith(messageType)
1287+
val codecCall = vsApi {
1288+
irCall(
1289+
type = codecType.makeNullable(),
1290+
callee = ctx.functions.grpcMessageCodecResolverResolveOrNull.symbol,
12791291
typeArgumentsCount = 0,
1280-
valueArgumentsCount = 1,
12811292
)
12821293
}.apply {
12831294
arguments {
@@ -1289,10 +1300,21 @@ internal class RpcStubGenerator(
12891300
)
12901301

12911302
values {
1292-
+irTypeOfCall(type)
1303+
+irTypeOfCall(messageType)
12931304
}
12941305
}
12951306
}
1307+
1308+
irElvis(
1309+
expression = codecCall,
1310+
ifNull = irCall(ctx.irBuiltIns.illegalArgumentExceptionSymbol).apply {
1311+
arguments {
1312+
values {
1313+
+stringConst("No codec found for ${messageType.classFqName}")
1314+
}
1315+
}
1316+
},
1317+
)
12961318
}
12971319
}
12981320

@@ -1809,4 +1831,31 @@ internal class RpcStubGenerator(
18091831

18101832
fun IrBuilderWithScope.irSafeAs(argument: IrExpression, type: IrType) =
18111833
IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.SAFE_CAST, type, argument)
1834+
1835+
fun IrBlockBodyBuilder.irElvis(expression: IrExpression, ifNull: IrExpression): IrExpression {
1836+
check(expression.type == ifNull.type || ifNull.type == ctx.irBuiltIns.nothingType) {
1837+
"Type mismatch: ${expression.type.dumpKotlinLike()} != ${ifNull.type.dumpKotlinLike()}"
1838+
}
1839+
1840+
return irBlock(origin = IrStatementOrigin.ELVIS, resultType = expression.type.makeNotNull()) {
1841+
val temp = irTemporary(
1842+
value = expression,
1843+
nameHint = "elvis_left_hand_side",
1844+
isMutable = false,
1845+
)
1846+
+irWhen(
1847+
type = expression.type,
1848+
branches = listOf(
1849+
irBranch(
1850+
condition = irEqualsNull(irGet(temp)),
1851+
result = ifNull,
1852+
),
1853+
irBranch(
1854+
condition = irTrue(),
1855+
result = irGet(temp),
1856+
),
1857+
),
1858+
)
1859+
}
1860+
}
18121861
}

compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/ServiceDeclaration.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import kotlinx.rpc.codegen.common.RpcClassId
88
import org.jetbrains.kotlin.ir.declarations.IrClass
99
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
1010
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
11+
import org.jetbrains.kotlin.ir.expressions.IrConst
1112
import org.jetbrains.kotlin.ir.types.IrType
1213
import org.jetbrains.kotlin.ir.util.defaultType
14+
import org.jetbrains.kotlin.ir.util.getAnnotation
15+
import org.jetbrains.kotlin.ir.util.getValueArgument
1316
import org.jetbrains.kotlin.ir.util.hasAnnotation
1417
import org.jetbrains.kotlin.ir.util.kotlinFqName
18+
import org.jetbrains.kotlin.name.Name
1519

16-
class ServiceDeclaration(
20+
internal class ServiceDeclaration(
1721
val service: IrClass,
1822
val stubClass: IrClass,
1923
val methods: List<Method>,
@@ -37,6 +41,17 @@ class ServiceDeclaration(
3741
val arguments: List<Argument>,
3842
) : Callable {
3943
override val name: String = function.name.asString()
44+
val grpcName by lazy {
45+
val grpcMethodAnnotation = function.getAnnotation(
46+
RpcClassId.grpcMethodAnnotation.asSingleFqName(),
47+
)
48+
49+
val nameArgument = grpcMethodAnnotation?.getValueArgument(Name.identifier("name"))
50+
51+
((nameArgument as? IrConst)?.value as? String)
52+
?.takeIf { it.isNotBlank() }
53+
?: name
54+
}
4055

4156
class Argument(
4257
val value: IrValueParameter,

compiler-plugin/compiler-plugin-common/src/main/kotlin/kotlinx/rpc/codegen/common/Names.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.name.Name
1111
object RpcClassId {
1212
val rpcAnnotation = ClassId(FqName("kotlinx.rpc.annotations"), Name.identifier("Rpc"))
1313
val grpcAnnotation = ClassId(FqName("kotlinx.rpc.grpc.annotations"), Name.identifier("Grpc"))
14+
val grpcMethodAnnotation = ClassId(FqName("kotlinx.rpc.grpc.annotations"), Name.identifier("Grpc.Method"))
1415
val withCodecAnnotation = ClassId(FqName("kotlinx.rpc.grpc.codec"), Name.identifier("WithCodec"))
1516
val checkedTypeAnnotation = ClassId(FqName("kotlinx.rpc.annotations"), Name.identifier("CheckedTypeAnnotation"))
1617

compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirGrpcServiceDeclarationChecker.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@
55
package kotlinx.rpc.codegen.checkers
66

77
import kotlinx.rpc.codegen.FirRpcPredicates
8+
import kotlinx.rpc.codegen.checkers.IdentifierRegexes.identifierRegex
9+
import kotlinx.rpc.codegen.checkers.IdentifierRegexes.packageIdentifierRegex
810
import kotlinx.rpc.codegen.checkers.diagnostics.FirGrpcDiagnostics
911
import kotlinx.rpc.codegen.checkers.util.functionParametersRecursionCheck
1012
import kotlinx.rpc.codegen.common.RpcClassId
1113
import kotlinx.rpc.codegen.vsApi
14+
import org.jetbrains.kotlin.descriptors.ClassKind
1215
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
16+
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
1317
import org.jetbrains.kotlin.diagnostics.reportOn
1418
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
1519
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
20+
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
21+
import org.jetbrains.kotlin.fir.declarations.getBooleanArgument
22+
import org.jetbrains.kotlin.fir.declarations.getStringArgument
1623
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
1724
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
1825
import org.jetbrains.kotlin.fir.types.isMarkedNullable
26+
import org.jetbrains.kotlin.name.Name
1927

2028
object FirGrpcServiceDeclarationChecker {
2129
fun check(
@@ -27,6 +35,26 @@ object FirGrpcServiceDeclarationChecker {
2735
return
2836
}
2937

38+
val isMetaAnnotated = declaration.classKind == ClassKind.ANNOTATION_CLASS
39+
if (isMetaAnnotated) {
40+
return
41+
}
42+
43+
val annotation = declaration.getAnnotationByClassId(RpcClassId.grpcAnnotation, context.session)
44+
?: error("Unexpected unresolved @Grpc annotation type for declaration: ${declaration.symbol.classId.asSingleFqName()}")
45+
46+
val protoPackage = annotation.getStringArgument(protoPackageName, context.session).orEmpty()
47+
48+
if (protoPackage.isNotEmpty()) {
49+
if (!protoPackage.matches(packageIdentifierRegex)) {
50+
reporter.reportOn(
51+
source = annotation.argumentMapping.mapping[protoPackageName]!!.source,
52+
factory = FirGrpcDiagnostics.WRONG_PROTO_PACKAGE_VALUE,
53+
context = context,
54+
)
55+
}
56+
}
57+
3058
vsApi {
3159
declaration
3260
.declarationsVS(context.session)
@@ -71,6 +99,48 @@ object FirGrpcServiceDeclarationChecker {
7199
context = context,
72100
)
73101
}
102+
103+
val grpcMethodAnnotation = function.getAnnotationByClassId(RpcClassId.grpcMethodAnnotation, context.session)
104+
105+
if (grpcMethodAnnotation != null) {
106+
val grpcMethodName = grpcMethodAnnotation.getStringArgument(grpcMethodNameName, context.session).orEmpty()
107+
108+
if (grpcMethodName.isNotEmpty()) {
109+
if (!grpcMethodName.matches(identifierRegex)) {
110+
reporter.reportOn(
111+
source = grpcMethodAnnotation.argumentMapping.mapping[grpcMethodNameName]!!.source,
112+
factory = FirGrpcDiagnostics.WRONG_PROTO_METHOD_NAME_VALUE,
113+
context = context,
114+
)
115+
}
116+
}
117+
118+
val safe = grpcMethodAnnotation.getBooleanArgument(grpcMethodSafeName, context.session) ?: false
119+
val idempotent = grpcMethodAnnotation.getBooleanArgument(grpcMethodIdempotentName, context.session) ?: false
120+
121+
// safe = true, idempotent = true is allowed
122+
// safe = false, idempotent = false is allowed
123+
// safe = false, idempotent = true is allowed
124+
// safe = true, idempotent = false is not allowed
125+
if (safe && !idempotent) {
126+
reporter.reportOn(
127+
source = grpcMethodAnnotation.source,
128+
factory = FirGrpcDiagnostics.WRONG_SAFE_IDEMPOTENT_COMBINATION,
129+
context = context,
130+
positioningStrategy = SourceElementPositioningStrategies.VALUE_ARGUMENTS_LIST,
131+
)
132+
}
133+
}
74134
}
75135
}
136+
137+
private val protoPackageName = Name.identifier("protoPackage")
138+
private val grpcMethodNameName = Name.identifier("name")
139+
private val grpcMethodSafeName = Name.identifier("safe")
140+
private val grpcMethodIdempotentName = Name.identifier("idempotent")
141+
}
142+
143+
object IdentifierRegexes {
144+
val identifierRegex = Regex("[a-zA-Z_][a-zA-Z0-9_]*")
145+
val packageIdentifierRegex = Regex("[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*")
76146
}

compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirRpcServiceDeclarationChecker.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ object FirRpcServiceDeclarationChecker {
6262
}
6363
.groupBy { it.name }
6464
.filter { (_, list) -> list.size > 1 }
65-
.forEach { name, functions ->
65+
.forEach { (name, functions) ->
6666
functions.forEach { function ->
6767
reporter.reportOn(
6868
source = function.source,

compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ object FirRpcStrictModeDiagnostics : RpcKtDiagnosticsContainer() {
5151
}
5252

5353
object FirGrpcDiagnostics : RpcKtDiagnosticsContainer() {
54+
val WRONG_PROTO_PACKAGE_VALUE by error0<KtElement>()
55+
val WRONG_PROTO_METHOD_NAME_VALUE by error0<KtElement>()
56+
val WRONG_SAFE_IDEMPOTENT_COMBINATION by error0<KtElement>()
5457
val NULLABLE_PARAMETER_IN_GRPC_SERVICE by error0<KtElement>()
5558
val NULLABLE_RETURN_TYPE_IN_GRPC_SERVICE by error0<KtElement>()
5659
val NON_TOP_LEVEL_CLIENT_STREAMING_IN_RPC_SERVICE by error0<KtElement>()

0 commit comments

Comments
 (0)