Skip to content

Commit 7b70a88

Browse files
committed
Add destructuring checks and annotation to skip
1 parent 6d8c43b commit 7b70a88

File tree

5 files changed

+232
-0
lines changed

5 files changed

+232
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.sschr15.aoc.annotations
2+
3+
/**
4+
* Marks a function, class, or file to skip destructuring checks.
5+
*
6+
* When using this compiler plugin, the plugin will check that all elements of a destructuring declaration are used
7+
* if the destructed item is a [Collection].
8+
* If this behavior is not desired, adding `@SkipDestructuringChecks` will disable it.
9+
*/
10+
@Retention(AnnotationRetention.SOURCE)
11+
annotation class SkipDestructuringChecks
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.sschr15.aoc.compiler.internal
2+
3+
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
4+
import org.jetbrains.kotlin.ir.types.classFqName
5+
import org.jetbrains.kotlin.ir.types.defaultType
6+
import org.jetbrains.kotlin.ir.types.starProjectedType
7+
import org.jetbrains.kotlin.ir.util.functions
8+
import org.jetbrains.kotlin.ir.util.getPropertyGetter
9+
import org.jetbrains.kotlin.ir.util.properties
10+
import org.jetbrains.kotlin.name.ClassId
11+
import org.jetbrains.kotlin.name.FqName
12+
import org.jetbrains.kotlin.name.Name
13+
14+
class ExtraPluginStuff(val pluginContext: IrPluginContext) {
15+
val unitType = pluginContext.irBuiltIns.unitType
16+
val booleanType = pluginContext.irBuiltIns.booleanType
17+
val intType = pluginContext.irBuiltIns.intType
18+
val stringType = pluginContext.irBuiltIns.stringType
19+
20+
val collectionClass = pluginContext.irBuiltIns.collectionClass
21+
val collectionStarType = collectionClass.starProjectedType
22+
23+
val printStreamClass = pluginContext.referenceClass(ClassId(FqName("java.io"), Name.identifier("PrintStream")))!!
24+
val printStreamType = printStreamClass.defaultType
25+
26+
val systemClass = pluginContext.referenceClass(ClassId(FqName("java.lang"), Name.identifier("System")))!!
27+
val systemErrField = systemClass.owner.properties.single { it.name.asString() == "err" }.backingField!!.symbol
28+
29+
val andAnd = pluginContext.irBuiltIns.andandSymbol
30+
val eqEq = pluginContext.irBuiltIns.eqeqSymbol
31+
val not = pluginContext.irBuiltIns.booleanNotSymbol
32+
33+
val sizeFunctionSymbol = collectionClass.getPropertyGetter("size")!!
34+
val printStreamPrintlnFunctionSymbol =
35+
printStreamClass.functions.single { it.owner.name.asString() == "println" && it.owner.valueParameters.singleOrNull()?.type == pluginContext.irBuiltIns.anyNType }
36+
37+
val illegalArgumentExceptionCtor = (
38+
pluginContext.referenceConstructors(ClassId(FqName("kotlin"), Name.identifier("IllegalArgumentException"))) +
39+
pluginContext.referenceConstructors(ClassId(FqName("java.lang"), Name.identifier("IllegalArgumentException")))
40+
).first { it.owner.valueParameters.singleOrNull()?.type?.classFqName?.asString()?.matches("""(kotlin|java\.lang)\.String""".toRegex()) == true }
41+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.sschr15.aoc.compiler.internal
2+
3+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
4+
import org.jetbrains.kotlin.cli.jvm.compiler.report
5+
import org.jetbrains.kotlin.config.CompilerConfiguration
6+
import org.jetbrains.kotlin.config.CompilerConfigurationKey
7+
import org.jetbrains.kotlin.fir.FirSession
8+
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
9+
import org.jetbrains.kotlin.fir.declarations.FirFunction
10+
import org.jetbrains.kotlin.fir.declarations.FirProperty
11+
import org.jetbrains.kotlin.fir.expressions.FirComponentCall
12+
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
13+
import org.jetbrains.kotlin.fir.extensions.FirStatusTransformerExtension
14+
import org.jetbrains.kotlin.fir.packageFqName
15+
import org.jetbrains.kotlin.fir.renderWithType
16+
import org.jetbrains.kotlin.name.FqName
17+
import org.jetbrains.kotlin.name.SpecialNames
18+
19+
class DestructuringFinder(session: FirSession, val config: CompilerConfiguration) : FirStatusTransformerExtension(session) {
20+
override fun needTransformStatus(declaration: FirDeclaration): Boolean {
21+
if (declaration is FirFunction) {
22+
val body = declaration.body ?: return false
23+
var currentDestruct: FirProperty? = null
24+
var currentDestructCount = 0
25+
val destructs = mutableListOf<Int>()
26+
27+
//TODO: handle destructuring in parameters
28+
// val destructedParams = mutableListOf<FirValueParameter>()
29+
//
30+
// for (parameter in declaration.valueParameters) {
31+
// if (parameter.name == SpecialNames.DESTRUCT) {
32+
// destructedParams.add(parameter)
33+
// }
34+
// }
35+
36+
for (statement in body.statements) {
37+
if (statement !is FirProperty) continue
38+
39+
if (statement.name == SpecialNames.DESTRUCT) {
40+
if (currentDestruct != null) {
41+
destructs.add(currentDestructCount)
42+
}
43+
currentDestruct = statement
44+
currentDestructCount = 0
45+
continue
46+
}
47+
48+
if (currentDestruct == null) continue
49+
50+
val initializer = statement.initializer
51+
config.report(CompilerMessageSeverity.STRONG_WARNING, statement.renderWithType())
52+
config.report(CompilerMessageSeverity.STRONG_WARNING, initializer?.renderWithType() ?: "no initializer")
53+
if (initializer !is FirComponentCall) continue
54+
55+
currentDestructCount++
56+
}
57+
58+
if (currentDestruct != null) {
59+
destructs.add(currentDestructCount)
60+
}
61+
62+
config.put(DestructuringFinder, declaration.symbol.packageFqName().child(declaration.symbol.name), destructs)
63+
}
64+
65+
return true
66+
}
67+
68+
companion object : CompilerConfigurationKey<Map<FqName, List<Int>>>("unused-destructuring-vars")
69+
}
70+
71+
class FirRegistrar(val config: CompilerConfiguration) : FirExtensionRegistrar() {
72+
override fun ExtensionRegistrarContext.configurePlugin() {
73+
+{ session: FirSession -> DestructuringFinder(session, config) }
74+
}
75+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.sschr15.aoc.compiler.internal
2+
3+
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
4+
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
5+
import org.jetbrains.kotlin.backend.common.lower.irIfThen
6+
import org.jetbrains.kotlin.backend.common.lower.irThrow
7+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
8+
import org.jetbrains.kotlin.cli.jvm.compiler.report
9+
import org.jetbrains.kotlin.config.CompilerConfiguration
10+
import org.jetbrains.kotlin.ir.IrStatement
11+
import org.jetbrains.kotlin.ir.builders.*
12+
import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
13+
import org.jetbrains.kotlin.ir.declarations.IrFunction
14+
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
15+
import org.jetbrains.kotlin.ir.declarations.IrVariable
16+
import org.jetbrains.kotlin.ir.expressions.IrSyntheticBody
17+
import org.jetbrains.kotlin.ir.types.starProjectedType
18+
import org.jetbrains.kotlin.ir.util.*
19+
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
20+
import org.jetbrains.kotlin.name.FqName
21+
import org.jetbrains.kotlin.name.SpecialNames
22+
23+
class IrDestructuringFinder(
24+
val context: IrPluginContext,
25+
val config: CompilerConfiguration,
26+
) : IrElementTransformerVoid() {
27+
override fun visitDeclaration(declaration: IrDeclarationBase): IrStatement {
28+
if (declaration.annotations.any { it.isAnnotationWithEqualFqName(FqName("com.sschr15.aoc.annotations.SkipDestructuringChecks")) }) {
29+
return declaration
30+
}
31+
32+
return super.visitDeclaration(declaration)
33+
}
34+
35+
val extra = ExtraPluginStuff(context)
36+
val destructuringMap = config.getMap(DestructuringFinder)
37+
38+
override fun visitFunction(declaration: IrFunction): IrStatement {
39+
if (declaration.annotations.any { it.isAnnotationWithEqualFqName(FqName("com.sschr15.aoc.annotations.SkipDestructuringChecks")) }) {
40+
return declaration
41+
}
42+
43+
val destructs = destructuringMap[declaration.getPackageFragment().packageFqName.child(declaration.name)]
44+
?: return super.visitFunction(declaration)
45+
46+
var currentDestruct = 0
47+
if (declaration.body is IrSyntheticBody) return super.visitFunction(declaration) // synthetic bodies have no statements
48+
val statements = declaration.body?.statements?.toMutableList() ?: return super.visitFunction(declaration)
49+
50+
fun runOnStatement(stat: IrValueDeclaration): IrStatement {
51+
val expected = destructs[currentDestruct++]
52+
return context.irBuiltIns.createIrBuilder(stat.symbol, stat.startOffset, stat.endOffset).irBlock {
53+
+irIfThen(
54+
condition = irCall(context.irBuiltIns.andandSymbol).apply {
55+
putValueArgument(0, irIs(irGet(stat), context.irBuiltIns.collectionClass.starProjectedType))
56+
putValueArgument(1, irNotEquals(
57+
irCall(extra.sizeFunctionSymbol).apply {
58+
dispatchReceiver = irGet(stat)
59+
},
60+
irInt(expected)
61+
))
62+
},
63+
thenPart=irThrow(irCallConstructor(extra.illegalArgumentExceptionCtor, emptyList()).apply {
64+
putValueArgument(0, irConcat().apply {
65+
arguments.addAll(listOf(
66+
irString("expected to destruct $expected elements, but "),
67+
irCall(extra.sizeFunctionSymbol).apply {
68+
dispatchReceiver = irGet(stat)
69+
},
70+
irString(" were provided")
71+
))
72+
})
73+
}),
74+
)
75+
}
76+
}
77+
78+
//TODO: handle destructuring in parameters
79+
// statements.addAll(
80+
// 0,
81+
// declaration.valueParameters
82+
// .filter { it.name == SpecialNames.DESTRUCT }
83+
// .map { stat -> runOnStatement(stat) }
84+
// )
85+
86+
statements.transformFlat { stat ->
87+
if (stat !is IrVariable) return@transformFlat null
88+
if (stat.name != SpecialNames.DESTRUCT) return@transformFlat null
89+
if (currentDestruct >= destructs.size) {
90+
config.report(CompilerMessageSeverity.STRONG_WARNING, "Destructuring ${stat.dumpKotlinLike()}, but no destructuring count was provided")
91+
return@transformFlat null
92+
}
93+
94+
listOf(
95+
stat,
96+
runOnStatement(stat),
97+
)
98+
}
99+
return super.visitFunction(declaration)
100+
}
101+
}

compiler-plugin/src/main/kotlin/PluginRegistrar.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
44
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
55
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
66
import org.jetbrains.kotlin.config.CompilerConfiguration
7+
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
78
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
89

910
class PluginRegistrar : CompilerPluginRegistrar() {
@@ -12,10 +13,13 @@ class PluginRegistrar : CompilerPluginRegistrar() {
1213
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
1314
// error("PAIN")
1415

16+
FirExtensionRegistrarAdapter.registerExtension(FirRegistrar(configuration))
17+
1518
IrGenerationExtension.registerExtension(object : IrGenerationExtension {
1619
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
1720
moduleFragment.transform(Memoizer(pluginContext), null)
1821
moduleFragment.transform(OverflowUnderflowChecker(pluginContext, configuration), null)
22+
moduleFragment.transform(IrDestructuringFinder(pluginContext, configuration), null)
1923
}
2024
})
2125
}

0 commit comments

Comments
 (0)