Skip to content

Commit 68887e1

Browse files
authored
Grpc.Method Annotation (#487)
1 parent a284423 commit 68887e1

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
@@ -1070,12 +1070,12 @@ internal class RpcStubGenerator(
10701070
* Where:
10711071
* - `<callable-name-k>` - the name of the k-th callable in the service
10721072
*/
1073-
private fun irMethodDescriptorMap(resolver: IrValueParameter): IrCallImpl {
1073+
private fun IrBlockBodyBuilder.irMethodDescriptorMap(resolver: IrValueParameter): IrCallImpl {
10741074
return irMapOf(
10751075
keyType = ctx.irBuiltIns.stringType,
10761076
valueType = ctx.grpcPlatformMethodDescriptor.starProjectedType,
10771077
declaration.methods.memoryOptimizedMap { callable ->
1078-
stringConst(callable.name) to irMethodDescriptor(callable, resolver)
1078+
stringConst(callable.grpcName) to irMethodDescriptor(callable, resolver)
10791079
},
10801080
)
10811081
}
@@ -1129,15 +1129,14 @@ internal class RpcStubGenerator(
11291129
* // In scope: resolver: MessageCodecResolver
11301130
*
11311131
* methodDescriptor<<request-type>, <response-type>>(
1132-
* fullMethodName = "${descriptor.serviceFqName}/${callable.name}",
1132+
* fullMethodName = "${descriptor.serviceFqName}/${<from Grpc.Method annotation> ?: callable.name}",
11331133
* requestCodec = <request-codec>,
11341134
* responseCodec = <response-codec>,
11351135
* type = MethodType.<method-type>,
11361136
* schemaDescriptor = null, // null for now
1137-
* // todo understand these values
1138-
* idempotent = true,
1139-
* safe = true,
1140-
* sampledToLocalTracing = true,
1137+
* idempotent = <from Grpc.Method annotation>,
1138+
* safe = <from Grpc.Method annotation>,
1139+
* sampledToLocalTracing = <from Grpc.Method annotation>,
11411140
* )
11421141
* ```
11431142
*
@@ -1149,7 +1148,10 @@ internal class RpcStubGenerator(
11491148
* MethodType.CLIENT_STREAMING, MethodType.BIDI_STREAMING
11501149
* - <request-codec>/<response-codec> - a MessageCodec getter, see [irCodec]
11511150
*/
1152-
private fun irMethodDescriptor(callable: ServiceDeclaration.Callable, resolver: IrValueParameter): IrCall {
1151+
private fun IrBlockBodyBuilder.irMethodDescriptor(
1152+
callable: ServiceDeclaration.Callable,
1153+
resolver: IrValueParameter,
1154+
): IrCall {
11531155
check(callable is ServiceDeclaration.Method) {
11541156
"Only methods are allowed here"
11551157
}
@@ -1167,13 +1169,10 @@ internal class RpcStubGenerator(
11671169
val methodDescriptorType = ctx.grpcPlatformMethodDescriptor.typeWith(requestType, responseType)
11681170

11691171
return vsApi {
1170-
IrCallImplVS(
1171-
startOffset = UNDEFINED_OFFSET,
1172-
endOffset = UNDEFINED_OFFSET,
1172+
irCall(
11731173
type = methodDescriptorType,
1174-
symbol = ctx.functions.methodDescriptor,
1174+
callee = ctx.functions.methodDescriptor,
11751175
typeArgumentsCount = 2,
1176-
valueArgumentsCount = 8,
11771176
)
11781177
}.apply {
11791178
arguments {
@@ -1182,9 +1181,12 @@ internal class RpcStubGenerator(
11821181
+responseType
11831182
}
11841183

1184+
val grpcMethodAnnotation = callable.function
1185+
.getAnnotation(RpcClassId.grpcMethodAnnotation.asSingleFqName())
1186+
11851187
values {
11861188
// fullMethodName
1187-
+stringConst("${declaration.serviceFqName}/${callable.name}")
1189+
+stringConst("${declaration.serviceFqName}/${callable.grpcName}")
11881190

11891191
// requestCodec
11901192
+irCodec(requestType, resolver)
@@ -1219,28 +1221,39 @@ internal class RpcStubGenerator(
12191221
// schemaDescriptor
12201222
+nullConst(ctx.anyNullable)
12211223

1222-
// todo figure out these
1224+
val idempotentArgument = grpcMethodAnnotation
1225+
?.getValueArgument(Name.identifier("idempotent"))
1226+
val idempotent = (idempotentArgument as? IrConst)?.value as? Boolean ?: false
1227+
12231228
// idempotent
1224-
+booleanConst(true)
1229+
+booleanConst(idempotent)
1230+
1231+
val safeArgument = grpcMethodAnnotation
1232+
?.getValueArgument(Name.identifier("safe"))
1233+
val safe = (safeArgument as? IrConst)?.value as? Boolean ?: false
12251234

12261235
// safe
1227-
+booleanConst(true)
1236+
+booleanConst(safe)
1237+
1238+
val sampledToLocalTracingArgument = grpcMethodAnnotation
1239+
?.getValueArgument(Name.identifier("sampledToLocalTracing"))
1240+
val sampledToLocalTracing = (sampledToLocalTracingArgument as? IrConst)?.value as? Boolean ?: true
12281241

12291242
// sampledToLocalTracing
1230-
+booleanConst(true)
1243+
+booleanConst(sampledToLocalTracing)
12311244
}
12321245
}
12331246
}
12341247
}
12351248

12361249
/**
1237-
* If [type] is annotated with [RpcIrContext.withCodecAnnotation],
1250+
* If [messageType] is annotated with [RpcIrContext.withCodecAnnotation],
12381251
* we use its codec object
12391252
*
1240-
* If not, use [resolver].resolve()
1253+
* If not, use [resolver].resolveOrNull()
12411254
*/
1242-
private fun irCodec(type: IrType, resolver: IrValueParameter): IrExpression {
1243-
val owner = type.classOrFail.owner
1255+
private fun IrBlockBodyBuilder.irCodec(messageType: IrType, resolver: IrValueParameter): IrExpression {
1256+
val owner = messageType.classOrFail.owner
12441257
val protobufMessage = owner.getAnnotation(ctx.withCodecAnnotation.owner.kotlinFqName)
12451258

12461259
return if (protobufMessage != null) {
@@ -1256,14 +1269,12 @@ internal class RpcStubGenerator(
12561269
symbol = codec.classOrFail,
12571270
)
12581271
} else {
1259-
vsApi {
1260-
IrCallImplVS(
1261-
startOffset = UNDEFINED_OFFSET,
1262-
endOffset = UNDEFINED_OFFSET,
1263-
type = ctx.grpcMessageCodec.typeWith(type),
1264-
symbol = ctx.functions.grpcMessageCodecResolverResolveOrNull.symbol,
1272+
val codecType = ctx.grpcMessageCodec.typeWith(messageType)
1273+
val codecCall = vsApi {
1274+
irCall(
1275+
type = codecType.makeNullable(),
1276+
callee = ctx.functions.grpcMessageCodecResolverResolveOrNull.symbol,
12651277
typeArgumentsCount = 0,
1266-
valueArgumentsCount = 1,
12671278
)
12681279
}.apply {
12691280
arguments {
@@ -1275,10 +1286,21 @@ internal class RpcStubGenerator(
12751286
)
12761287

12771288
values {
1278-
+irTypeOfCall(type)
1289+
+irTypeOfCall(messageType)
12791290
}
12801291
}
12811292
}
1293+
1294+
irElvis(
1295+
expression = codecCall,
1296+
ifNull = irCall(ctx.irBuiltIns.illegalArgumentExceptionSymbol).apply {
1297+
arguments {
1298+
values {
1299+
+stringConst("No codec found for ${messageType.classFqName}")
1300+
}
1301+
}
1302+
},
1303+
)
12821304
}
12831305
}
12841306

@@ -1795,4 +1817,31 @@ internal class RpcStubGenerator(
17951817

17961818
fun IrBuilderWithScope.irSafeAs(argument: IrExpression, type: IrType) =
17971819
IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.SAFE_CAST, type, argument)
1820+
1821+
fun IrBlockBodyBuilder.irElvis(expression: IrExpression, ifNull: IrExpression): IrExpression {
1822+
check(expression.type == ifNull.type || ifNull.type == ctx.irBuiltIns.nothingType) {
1823+
"Type mismatch: ${expression.type.dumpKotlinLike()} != ${ifNull.type.dumpKotlinLike()}"
1824+
}
1825+
1826+
return irBlock(origin = IrStatementOrigin.ELVIS, resultType = expression.type.makeNotNull()) {
1827+
val temp = irTemporary(
1828+
value = expression,
1829+
nameHint = "elvis_left_hand_side",
1830+
isMutable = false,
1831+
)
1832+
+irWhen(
1833+
type = expression.type,
1834+
branches = listOf(
1835+
irBranch(
1836+
condition = irEqualsNull(irGet(temp)),
1837+
result = ifNull,
1838+
),
1839+
irBranch(
1840+
condition = irTrue(),
1841+
result = irGet(temp),
1842+
),
1843+
),
1844+
)
1845+
}
1846+
}
17981847
}

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)