Skip to content

Commit

Permalink
Compiler-enforce interfaces cannot extend ZiplineScoped (#785)
Browse files Browse the repository at this point in the history
Closes: #775
  • Loading branch information
squarejesse authored Dec 21, 2022
1 parent 3c1943b commit 3ace68d
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal class ZiplineApis(
private val serializersModuleFqName = serializationModulesFqName.child("SerializersModule")
private val ziplineFqName = packageFqName.child("Zipline")
private val outboundServiceFqName = bridgeFqName.child("OutboundService")
val ziplineScopedFqName = packageFqName.child("ZiplineScoped")
val ziplineServiceFqName = packageFqName.child("ZiplineService")
private val ziplineServiceSerializerFunctionFqName =
packageFqName.child("ziplineServiceSerializer")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isInterface

class ZiplineIrGenerationExtension(
Expand All @@ -38,6 +39,16 @@ class ZiplineIrGenerationExtension(
val declaration = super.visitClassNew(declaration) as IrClass

try {
if (declaration.isInterface &&
declaration.superTypes.any { it.classFqName == ziplineApis.ziplineScopedFqName }
) {
throw ZiplineCompilationException(
element = declaration,
message = "Only classes may implement ZiplineScoped, but" +
" ${declaration.fqNameWhenAvailable} is an interface",
)
}

if (declaration.isInterface &&
declaration.superTypes.any { it.classFqName == ziplineApis.ziplineServiceFqName }
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,26 @@ class ZiplineKotlinPluginTest {
assertThat(adapterClass).isNotNull()
assertThat(adapterClass.interfaces).asList().containsExactly(KSerializer::class.java)
}

@Test
fun `interfaces cannot extend ZiplineScoped`() {
val result = compile(
sourceFile = SourceFile.kotlin(
"main.kt",
"""
package app.cash.zipline.testing
import app.cash.zipline.ZiplineScoped
interface SomeInterface : ZiplineScoped
"""
)
)
assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode, result.messages)
assertThat(result.messages)
.contains("(5, 1): Only classes may implement ZiplineScoped, but " +
"app.cash.zipline.testing.SomeInterface is an interface")
}
}

fun compile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal expect fun toInboundThrowable(
* Serialize a [Throwable] in two parts:
*
* * A list of types in preference-order. We'll only need one type in almost all cases, but if we
* want to introduce new throwable subtypes in the future this enables backwards=compatibility.
* want to introduce new throwable subtypes in the future this enables backwards-compatibility.
*
* * The throwable message and stacktrace, all together. This may be prefixed with the name
* of the throwable class.
Expand Down

0 comments on commit 3ace68d

Please sign in to comment.