Skip to content

Commit 6e14ea4

Browse files
committed
Added checkers for @Grpc.Method
1 parent dc1a9d0 commit 6e14ea4

File tree

9 files changed

+156
-10
lines changed

9 files changed

+156
-10
lines changed

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,6 @@ internal class RpcIrContext(
6464
getRpcIrClassSymbol("RpcServiceDescriptor", "descriptor")
6565
}
6666

67-
val grpcAnnotation by lazy {
68-
getIrClassSymbol("kotlinx.rpc.grpc.annotations", "Grpc")
69-
}
70-
71-
val grpcMethodAnnotation by lazy {
72-
grpcAnnotation.subClass("Method")
73-
}
74-
7567
val grpcServiceDescriptor by lazy {
7668
getIrClassSymbol("kotlinx.rpc.grpc.descriptor", "GrpcServiceDescriptor")
7769
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ internal class RpcStubGenerator(
11821182
}
11831183

11841184
val grpcMethodAnnotation = callable.function
1185-
.getAnnotation(ctx.grpcMethodAnnotation.owner.kotlinFqName)
1185+
.getAnnotation(RpcClassId.grpcMethodAnnotation.asSingleFqName())
11861186

11871187
values {
11881188
// fullMethodName

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ internal class ServiceDeclaration(
4444
override val name: String = function.name.asString()
4545
val grpcName by lazy {
4646
val grpcMethodAnnotation = function.getAnnotation(
47-
ctx.grpcMethodAnnotation.owner.kotlinFqName
47+
RpcClassId.grpcMethodAnnotation.asSingleFqName(),
4848
)
4949

5050
val nameArgument = grpcMethodAnnotation?.getValueArgument(Name.identifier("name"))

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/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>()

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package kotlinx.rpc.codegen.checkers.diagnostics
66

7+
import kotlinx.rpc.codegen.checkers.IdentifierRegexes
78
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
89
import org.jetbrains.kotlin.diagnostics.rendering.Renderer
910
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
@@ -102,6 +103,21 @@ object RpcStrictModeDiagnosticRendererFactory : BaseDiagnosticRendererFactory()
102103

103104
object GrpcDiagnosticRendererFactory : BaseDiagnosticRendererFactory() {
104105
override val MAP by RpcKtDiagnosticFactoryToRendererMap("Grpc") { map ->
106+
map.put(
107+
factory = FirGrpcDiagnostics.WRONG_PROTO_PACKAGE_VALUE,
108+
message = "'protoPackage' parameter value must be a valid package name (${IdentifierRegexes.packageIdentifierRegex.pattern}) or empty",
109+
)
110+
111+
map.put(
112+
factory = FirGrpcDiagnostics.WRONG_PROTO_METHOD_NAME_VALUE,
113+
message = "'name' parameter value must be a valid identifier (${IdentifierRegexes.identifierRegex.pattern}) or empty",
114+
)
115+
116+
map.put(
117+
factory = FirGrpcDiagnostics.WRONG_SAFE_IDEMPOTENT_COMBINATION,
118+
message = "'safe = true' and 'idempotent = false' are mutually exclusive.",
119+
)
120+
105121
map.put(
106122
factory = FirGrpcDiagnostics.NULLABLE_PARAMETER_IN_GRPC_SERVICE,
107123
message = "Nullable type is not allowed in @Grpc service function parameters.",

tests/compiler-plugin-tests/src/testData/diagnostics/grpc.fir.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,43 @@ FILE: module_main_grpc.kt
3434
public get(): R|kotlinx/coroutines/flow/Flow<kotlin/String>|
3535

3636
}
37+
@R|kotlinx/rpc/grpc/annotations/Grpc|(protoPackage = String(-invalid name)) public abstract interface WrongAnnotations1 : R|kotlin/Any| {
38+
public final class $rpcServiceStub : R|kotlin/Any| {
39+
public final companion object Companion : R|kotlin/Any| {
40+
}
41+
42+
}
43+
44+
}
45+
@R|kotlinx/rpc/grpc/annotations/Grpc|(protoPackage = String(-invalid name)) public abstract interface WrongAnnotations2 : R|kotlin/Any| {
46+
public final class $rpcServiceStub : R|kotlin/Any| {
47+
public final companion object Companion : R|kotlin/Any| {
48+
}
49+
50+
}
51+
52+
}
53+
@R|kotlinx/rpc/grpc/annotations/Grpc|(protoPackage = <strcat>(String(invalid.name), String( ))) public abstract interface WrongAnnotations3 : R|kotlin/Any| {
54+
public final class $rpcServiceStub : R|kotlin/Any| {
55+
public final companion object Companion : R|kotlin/Any| {
56+
}
57+
58+
}
59+
60+
}
61+
@R|kotlinx/rpc/grpc/annotations/Grpc|() public abstract interface WrongAnnotations4 : R|kotlin/Any| {
62+
@R|kotlinx/rpc/grpc/annotations/Grpc.Method|(name = String(wrongName+1)) public abstract suspend fun wrongName1(): R|kotlin/Unit|
63+
64+
@R|kotlinx/rpc/grpc/annotations/Grpc.Method|(name = String(wrongName+2)) public abstract suspend fun wrongName2(): R|kotlin/Unit|
65+
66+
@R|kotlinx/rpc/grpc/annotations/Grpc.Method|(name = <strcat>(String(wrongName), String(+3))) public abstract suspend fun wrongName3(): R|kotlin/Unit|
67+
68+
@R|kotlinx/rpc/grpc/annotations/Grpc.Method|(safe = Boolean(true), idempotent = Boolean(false)) public abstract suspend fun wrongSafeIdempotent(): R|kotlin/Unit|
69+
70+
public final class $rpcServiceStub : R|kotlin/Any| {
71+
public final companion object Companion : R|kotlin/Any| {
72+
}
73+
74+
}
75+
76+
}

tests/compiler-plugin-tests/src/testData/diagnostics/grpc.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,27 @@ interface MyService {
3131
}
3232

3333
class WithFlow(val flow: Flow<String>)
34+
35+
@Grpc(<!WRONG_PROTO_PACKAGE_VALUE!>"-invalid name"<!>)
36+
interface WrongAnnotations1
37+
38+
@Grpc(protoPackage = <!WRONG_PROTO_PACKAGE_VALUE!>"-invalid name"<!>)
39+
interface WrongAnnotations2
40+
41+
@Grpc(protoPackage = <!WRONG_PROTO_PACKAGE_VALUE!>"invalid.name" + " "<!>)
42+
interface WrongAnnotations3
43+
44+
@Grpc
45+
interface WrongAnnotations4 {
46+
@Grpc.Method(<!WRONG_PROTO_METHOD_NAME_VALUE!>"wrongName+1"<!>)
47+
suspend fun wrongName1()
48+
49+
@Grpc.Method(name = <!WRONG_PROTO_METHOD_NAME_VALUE!>"wrongName+2"<!>)
50+
suspend fun wrongName2()
51+
52+
@Grpc.Method(name = <!WRONG_PROTO_METHOD_NAME_VALUE!>"wrongName" + "+3"<!>)
53+
suspend fun wrongName3()
54+
55+
@Grpc.Method<!WRONG_SAFE_IDEMPOTENT_COMBINATION!>(safe = true, idempotent = false)<!>
56+
suspend fun wrongSafeIdempotent()
57+
}

0 commit comments

Comments
 (0)