Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cinterop-c/build_target.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ CONFIG=release
mkdir -p "$(dirname "$DST")"

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

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

cp -f "$out" "$DST"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ internal object RpcDeclarationScanner {
}

ServiceDeclaration.Method(
ctx = ctx,
function = declaration,
arguments = ctx.versionSpecificApi.run {
declaration.valueParametersVS().memoryOptimizedMap { param ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1070,12 +1070,12 @@ internal class RpcStubGenerator(
* Where:
* - `<callable-name-k>` - the name of the k-th callable in the service
*/
private fun irMethodDescriptorMap(resolver: IrValueParameter): IrCallImpl {
private fun IrBlockBodyBuilder.irMethodDescriptorMap(resolver: IrValueParameter): IrCallImpl {
return irMapOf(
keyType = ctx.irBuiltIns.stringType,
valueType = ctx.grpcPlatformMethodDescriptor.starProjectedType,
declaration.methods.memoryOptimizedMap { callable ->
stringConst(callable.name) to irMethodDescriptor(callable, resolver)
stringConst(callable.grpcName) to irMethodDescriptor(callable, resolver)
},
)
}
Expand Down Expand Up @@ -1129,15 +1129,14 @@ internal class RpcStubGenerator(
* // In scope: resolver: MessageCodecResolver
*
* methodDescriptor<<request-type>, <response-type>>(
* fullMethodName = "${descriptor.serviceFqName}/${callable.name}",
* fullMethodName = "${descriptor.serviceFqName}/${<from Grpc.Method annotation> ?: callable.name}",
* requestCodec = <request-codec>,
* responseCodec = <response-codec>,
* type = MethodType.<method-type>,
* schemaDescriptor = null, // null for now
* // todo understand these values
* idempotent = true,
* safe = true,
* sampledToLocalTracing = true,
* idempotent = <from Grpc.Method annotation>,
* safe = <from Grpc.Method annotation>,
* sampledToLocalTracing = <from Grpc.Method annotation>,
* )
* ```
*
Expand All @@ -1149,7 +1148,10 @@ internal class RpcStubGenerator(
* MethodType.CLIENT_STREAMING, MethodType.BIDI_STREAMING
* - <request-codec>/<response-codec> - a MessageCodec getter, see [irCodec]
*/
private fun irMethodDescriptor(callable: ServiceDeclaration.Callable, resolver: IrValueParameter): IrCall {
private fun IrBlockBodyBuilder.irMethodDescriptor(
callable: ServiceDeclaration.Callable,
resolver: IrValueParameter,
): IrCall {
check(callable is ServiceDeclaration.Method) {
"Only methods are allowed here"
}
Expand All @@ -1167,13 +1169,10 @@ internal class RpcStubGenerator(
val methodDescriptorType = ctx.grpcPlatformMethodDescriptor.typeWith(requestType, responseType)

return vsApi {
IrCallImplVS(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
irCall(
type = methodDescriptorType,
symbol = ctx.functions.methodDescriptor,
callee = ctx.functions.methodDescriptor,
typeArgumentsCount = 2,
valueArgumentsCount = 8,
)
}.apply {
arguments {
Expand All @@ -1182,9 +1181,12 @@ internal class RpcStubGenerator(
+responseType
}

val grpcMethodAnnotation = callable.function
.getAnnotation(RpcClassId.grpcMethodAnnotation.asSingleFqName())

values {
// fullMethodName
+stringConst("${declaration.serviceFqName}/${callable.name}")
+stringConst("${declaration.serviceFqName}/${callable.grpcName}")

// requestCodec
+irCodec(requestType, resolver)
Expand Down Expand Up @@ -1219,28 +1221,39 @@ internal class RpcStubGenerator(
// schemaDescriptor
+nullConst(ctx.anyNullable)

// todo figure out these
val idempotentArgument = grpcMethodAnnotation
?.getValueArgument(Name.identifier("idempotent"))
val idempotent = (idempotentArgument as? IrConst)?.value as? Boolean ?: false

// idempotent
+booleanConst(true)
+booleanConst(idempotent)

val safeArgument = grpcMethodAnnotation
?.getValueArgument(Name.identifier("safe"))
val safe = (safeArgument as? IrConst)?.value as? Boolean ?: false

// safe
+booleanConst(true)
+booleanConst(safe)

val sampledToLocalTracingArgument = grpcMethodAnnotation
?.getValueArgument(Name.identifier("sampledToLocalTracing"))
val sampledToLocalTracing = (sampledToLocalTracingArgument as? IrConst)?.value as? Boolean ?: true

// sampledToLocalTracing
+booleanConst(true)
+booleanConst(sampledToLocalTracing)
}
}
}
}

/**
* If [type] is annotated with [RpcIrContext.withCodecAnnotation],
* If [messageType] is annotated with [RpcIrContext.withCodecAnnotation],
* we use its codec object
*
* If not, use [resolver].resolve()
* If not, use [resolver].resolveOrNull()
*/
private fun irCodec(type: IrType, resolver: IrValueParameter): IrExpression {
val owner = type.classOrFail.owner
private fun IrBlockBodyBuilder.irCodec(messageType: IrType, resolver: IrValueParameter): IrExpression {
val owner = messageType.classOrFail.owner
val protobufMessage = owner.getAnnotation(ctx.withCodecAnnotation.owner.kotlinFqName)

return if (protobufMessage != null) {
Expand All @@ -1256,14 +1269,12 @@ internal class RpcStubGenerator(
symbol = codec.classOrFail,
)
} else {
vsApi {
IrCallImplVS(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
type = ctx.grpcMessageCodec.typeWith(type),
symbol = ctx.functions.grpcMessageCodecResolverResolveOrNull.symbol,
val codecType = ctx.grpcMessageCodec.typeWith(messageType)
val codecCall = vsApi {
irCall(
type = codecType.makeNullable(),
callee = ctx.functions.grpcMessageCodecResolverResolveOrNull.symbol,
typeArgumentsCount = 0,
valueArgumentsCount = 1,
)
}.apply {
arguments {
Expand All @@ -1275,10 +1286,21 @@ internal class RpcStubGenerator(
)

values {
+irTypeOfCall(type)
+irTypeOfCall(messageType)
}
}
}

irElvis(
expression = codecCall,
ifNull = irCall(ctx.irBuiltIns.illegalArgumentExceptionSymbol).apply {
arguments {
values {
+stringConst("No codec found for ${messageType.classFqName}")
}
}
},
)
}
}

Expand Down Expand Up @@ -1795,4 +1817,31 @@ internal class RpcStubGenerator(

fun IrBuilderWithScope.irSafeAs(argument: IrExpression, type: IrType) =
IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.SAFE_CAST, type, argument)

fun IrBlockBodyBuilder.irElvis(expression: IrExpression, ifNull: IrExpression): IrExpression {
check(expression.type == ifNull.type || ifNull.type == ctx.irBuiltIns.nothingType) {
"Type mismatch: ${expression.type.dumpKotlinLike()} != ${ifNull.type.dumpKotlinLike()}"
}

return irBlock(origin = IrStatementOrigin.ELVIS, resultType = expression.type.makeNotNull()) {
val temp = irTemporary(
value = expression,
nameHint = "elvis_left_hand_side",
isMutable = false,
)
+irWhen(
type = expression.type,
branches = listOf(
irBranch(
condition = irEqualsNull(irGet(temp)),
result = ifNull,
),
irBranch(
condition = irTrue(),
result = irGet(temp),
),
),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import kotlinx.rpc.codegen.common.RpcClassId
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.ir.util.getValueArgument
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.name.Name

class ServiceDeclaration(
internal class ServiceDeclaration(
val service: IrClass,
val stubClass: IrClass,
val methods: List<Method>,
Expand All @@ -33,10 +37,22 @@ class ServiceDeclaration(
}

class Method(
val ctx: RpcIrContext,
val function: IrSimpleFunction,
val arguments: List<Argument>,
) : Callable {
override val name: String = function.name.asString()
val grpcName by lazy {
val grpcMethodAnnotation = function.getAnnotation(
RpcClassId.grpcMethodAnnotation.asSingleFqName(),
)

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

((nameArgument as? IrConst)?.value as? String)
?.takeIf { it.isNotBlank() }
?: name
}

class Argument(
val value: IrValueParameter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.name.Name
object RpcClassId {
val rpcAnnotation = ClassId(FqName("kotlinx.rpc.annotations"), Name.identifier("Rpc"))
val grpcAnnotation = ClassId(FqName("kotlinx.rpc.grpc.annotations"), Name.identifier("Grpc"))
val grpcMethodAnnotation = ClassId(FqName("kotlinx.rpc.grpc.annotations"), Name.identifier("Grpc.Method"))
val withCodecAnnotation = ClassId(FqName("kotlinx.rpc.grpc.codec"), Name.identifier("WithCodec"))
val checkedTypeAnnotation = ClassId(FqName("kotlinx.rpc.annotations"), Name.identifier("CheckedTypeAnnotation"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@
package kotlinx.rpc.codegen.checkers

import kotlinx.rpc.codegen.FirRpcPredicates
import kotlinx.rpc.codegen.checkers.IdentifierRegexes.identifierRegex
import kotlinx.rpc.codegen.checkers.IdentifierRegexes.packageIdentifierRegex
import kotlinx.rpc.codegen.checkers.diagnostics.FirGrpcDiagnostics
import kotlinx.rpc.codegen.checkers.util.functionParametersRecursionCheck
import kotlinx.rpc.codegen.common.RpcClassId
import kotlinx.rpc.codegen.vsApi
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.declarations.getBooleanArgument
import org.jetbrains.kotlin.fir.declarations.getStringArgument
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.isMarkedNullable
import org.jetbrains.kotlin.name.Name

object FirGrpcServiceDeclarationChecker {
fun check(
Expand All @@ -27,6 +35,26 @@ object FirGrpcServiceDeclarationChecker {
return
}

val isMetaAnnotated = declaration.classKind == ClassKind.ANNOTATION_CLASS
if (isMetaAnnotated) {
return
}

val annotation = declaration.getAnnotationByClassId(RpcClassId.grpcAnnotation, context.session)
?: error("Unexpected unresolved @Grpc annotation type for declaration: ${declaration.symbol.classId.asSingleFqName()}")

val protoPackage = annotation.getStringArgument(protoPackageName, context.session).orEmpty()

if (protoPackage.isNotEmpty()) {
if (!protoPackage.matches(packageIdentifierRegex)) {
reporter.reportOn(
source = annotation.argumentMapping.mapping[protoPackageName]!!.source,
factory = FirGrpcDiagnostics.WRONG_PROTO_PACKAGE_VALUE,
context = context,
)
}
}

vsApi {
declaration
.declarationsVS(context.session)
Expand Down Expand Up @@ -71,6 +99,48 @@ object FirGrpcServiceDeclarationChecker {
context = context,
)
}

val grpcMethodAnnotation = function.getAnnotationByClassId(RpcClassId.grpcMethodAnnotation, context.session)

if (grpcMethodAnnotation != null) {
val grpcMethodName = grpcMethodAnnotation.getStringArgument(grpcMethodNameName, context.session).orEmpty()

if (grpcMethodName.isNotEmpty()) {
if (!grpcMethodName.matches(identifierRegex)) {
reporter.reportOn(
source = grpcMethodAnnotation.argumentMapping.mapping[grpcMethodNameName]!!.source,
factory = FirGrpcDiagnostics.WRONG_PROTO_METHOD_NAME_VALUE,
context = context,
)
}
}

val safe = grpcMethodAnnotation.getBooleanArgument(grpcMethodSafeName, context.session) ?: false
val idempotent = grpcMethodAnnotation.getBooleanArgument(grpcMethodIdempotentName, context.session) ?: false

// safe = true, idempotent = true is allowed
// safe = false, idempotent = false is allowed
// safe = false, idempotent = true is allowed
// safe = true, idempotent = false is not allowed
if (safe && !idempotent) {
reporter.reportOn(
source = grpcMethodAnnotation.source,
factory = FirGrpcDiagnostics.WRONG_SAFE_IDEMPOTENT_COMBINATION,
context = context,
positioningStrategy = SourceElementPositioningStrategies.VALUE_ARGUMENTS_LIST,
)
}
}
}
}

private val protoPackageName = Name.identifier("protoPackage")
private val grpcMethodNameName = Name.identifier("name")
private val grpcMethodSafeName = Name.identifier("safe")
private val grpcMethodIdempotentName = Name.identifier("idempotent")
}

object IdentifierRegexes {
val identifierRegex = Regex("[a-zA-Z_][a-zA-Z0-9_]*")
val packageIdentifierRegex = Regex("[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*")
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object FirRpcServiceDeclarationChecker {
}
.groupBy { it.name }
.filter { (_, list) -> list.size > 1 }
.forEach { name, functions ->
.forEach { (name, functions) ->
functions.forEach { function ->
reporter.reportOn(
source = function.source,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ object FirRpcStrictModeDiagnostics : RpcKtDiagnosticsContainer() {
}

object FirGrpcDiagnostics : RpcKtDiagnosticsContainer() {
val WRONG_PROTO_PACKAGE_VALUE by error0<KtElement>()
val WRONG_PROTO_METHOD_NAME_VALUE by error0<KtElement>()
val WRONG_SAFE_IDEMPOTENT_COMBINATION by error0<KtElement>()
val NULLABLE_PARAMETER_IN_GRPC_SERVICE by error0<KtElement>()
val NULLABLE_RETURN_TYPE_IN_GRPC_SERVICE by error0<KtElement>()
val NON_TOP_LEVEL_CLIENT_STREAMING_IN_RPC_SERVICE by error0<KtElement>()
Expand Down
Loading
Loading