From 723dbbc69aca6c644715f658426b216db8be662f Mon Sep 17 00:00:00 2001 From: Ji Sungbin Date: Tue, 1 Oct 2024 00:23:57 +0900 Subject: [PATCH] Reimplementing InvalidationTraceTableApiChecker --- .../InvalidationTraceTableApiChecker.kt | 106 ++++++------ ...alidationTraceTableIntrinsicTransformer.kt | 2 + .../_assert/DiagnosticsResultAsserter.kt | 9 +- .../traceTableApiUsage/composableFunction.kt | 9 +- .../traceTableApiUsage/composableLambda.kt | 19 ++- .../inlineComposableFunction.kt | 9 +- .../inlineComposableLambda.kt | 19 ++- .../inlineNormalFunction.kt | 9 +- .../traceTableApiUsage/inlineNormalLambda.kt | 19 ++- .../traceTableApiUsage/normalFunction.kt | 9 +- .../traceTableApiUsage/normalLambda.kt | 17 +- ...piCallWithAllNoInvestigationComposables.kt | 24 +++ .../topLevelApiCallWithComposable.kt | 21 +++ .../topLevelApiCallWithNoComposable.kt | 18 +++ ...validationTraceTableApiUsageCheckerTest.kt | 151 +++++------------- .../ComposableInvalidationTraceTable.kt | 7 +- .../runtime/ComposableScope.kt | 2 +- 17 files changed, 225 insertions(+), 225 deletions(-) create mode 100644 compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithAllNoInvestigationComposables.kt create mode 100644 compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithComposable.kt create mode 100644 compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithNoComposable.kt diff --git a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiChecker.kt b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiChecker.kt index 16636c25..f9f83ab5 100644 --- a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiChecker.kt +++ b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiChecker.kt @@ -9,100 +9,92 @@ package land.sungbin.composeinvestigator.compiler.frontend import androidx.compose.compiler.plugins.kotlin.k2.ComposableFunction import androidx.compose.compiler.plugins.kotlin.k2.hasComposableAnnotation -import androidx.compose.compiler.plugins.kotlin.lower.fastForEach -import land.sungbin.composeinvestigator.compiler.COMPOSABLE_INVALIDATION_TRACE_TABLE_FQN import land.sungbin.composeinvestigator.compiler.COMPOSABLE_NAME_FQN +import land.sungbin.composeinvestigator.compiler.COMPOSABLE_SCOPE_FQN +import land.sungbin.composeinvestigator.compiler.CURRENT_COMPOSABLE_INVALIDATION_TRACER_FQN import land.sungbin.composeinvestigator.compiler.NO_INVESTIGATION_FQN -import org.jetbrains.kotlin.KtSourceElement -import org.jetbrains.kotlin.diagnostics.AbstractKtDiagnosticFactory import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn -import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker +import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirPropertyAccessExpressionChecker import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction import org.jetbrains.kotlin.fir.declarations.hasAnnotation import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirLiteralExpression import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression import org.jetbrains.kotlin.fir.expressions.argument +import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.functionTypeKind import org.jetbrains.kotlin.fir.types.resolvedType -import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.utils.addToStdlib.unreachableBranch -// TODO add top-level usage checkers public class InvalidationTraceTableApiChecker(session: FirSession) : FirAdditionalCheckersExtension(session) { - override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() { - override val functionCheckers = setOf(InvalidationTraceTableApiUsageCheck) - } - override val expressionCheckers: ExpressionCheckers = object : ExpressionCheckers() { + override val propertyAccessExpressionCheckers = setOf(TraceTableApiAccessChecker) override val functionCallCheckers = setOf(ComposableNameExpressionChecker) } } -private object InvalidationTraceTableApiUsageCheck : FirFunctionChecker(MppCheckerKind.Common) { - // TODO we need to fix the real reason for the duplicate diagnostics. - private val handled = mutableMapOf>() +private object TraceTableApiAccessChecker : FirPropertyAccessExpressionChecker(MppCheckerKind.Common) { + private val NO_INVESTIGATION = ClassId.topLevel(NO_INVESTIGATION_FQN) + private val COMPOSABLE_SCOPE = ClassId.topLevel(COMPOSABLE_SCOPE_FQN) - override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) { - val hasNoInvestigation = context.containingFile!!.hasAnnotation(ClassId.topLevel(NO_INVESTIGATION_FQN), context.session) - val isComposableScope = when (declaration) { - is FirAnonymousFunction -> declaration.typeRef.coneType.functionTypeKind(context.session) === ComposableFunction - else -> declaration.hasComposableAnnotation(context.session) - } + override fun check(expression: FirPropertyAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { + // TODO when accessed by `it`, such as `traceTable.let { it.action() }`, + // Symbol is `FirValueParameterSymbol`. These variants need to be handled separately. + val callee = expression.calleeReference.toResolvedPropertySymbol() ?: return + + if ( + callee.callableId.asSingleFqName() == CURRENT_COMPOSABLE_INVALIDATION_TRACER_FQN && + context.isNoInvestigationFile() + ) + reporter.reportOn(expression.source, ComposeInvestigatorErrors.API_ACCESS_IN_NO_INVESTIGATION_FILE, context) - val visitor = TraceTableApiAccessVisiter(hasNoInvestigation, isComposableScope, context, reporter) - declaration.body?.statements?.fastForEach { statement -> statement.accept(visitor) } + checkComposableScopeCall(expression, callee, context, reporter) } - private class TraceTableApiAccessVisiter( - private val hasNoInvestigation: Boolean, - @Suppress("unused") private val isComposableScope: Boolean, - private val context: CheckerContext, - private val reporter: DiagnosticReporter, - ) : FirDefaultVisitorVoid() { - override fun visitElement(element: FirElement) { - element.acceptChildren(this) - } + private fun checkComposableScopeCall( + expression: FirPropertyAccessExpression, + callee: FirPropertySymbol, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + // FIXME the annotation for callee was not being looked up correctly. + // I don't know the cause yet, so I'm temporarily disabling this check. + return + + if ( + !callee.hasAnnotation(COMPOSABLE_SCOPE, context.session) && + callee.getterSymbol?.hasAnnotation(COMPOSABLE_SCOPE, context.session) != true && + callee.setterSymbol?.hasAnnotation(COMPOSABLE_SCOPE, context.session) != true + ) + return - override fun visitPropertyAccessExpression(expression: FirPropertyAccessExpression) { - if ( - (expression.dispatchReceiver?.resolvedType ?: expression.extensionReceiver?.resolvedType ?: expression.resolvedType) - .classId?.asSingleFqName() != COMPOSABLE_INVALIDATION_TRACE_TABLE_FQN - ) - return + if (!context.isComposableScope()) + reporter.reportOn(expression.source, ComposeInvestigatorErrors.ILLEGAL_COMPOSABLE_SCOPE_CALL, context) + } - if (hasNoInvestigation) { - if (handled.getOrPut(expression.source, ::mutableSetOf).add(ComposeInvestigatorErrors.API_ACCESS_IN_NO_INVESTIGATION_FILE)) - reporter.reportOn(expression.source, ComposeInvestigatorErrors.API_ACCESS_IN_NO_INVESTIGATION_FILE, context) - } + private fun CheckerContext.isNoInvestigationFile() = + containingFile!!.hasAnnotation(NO_INVESTIGATION, session) -// TODO need to rewrite logic to check if current function is in Composable scope. -// if ( -// expression.calleeReference -// .toResolvedPropertySymbol() -// ?.hasAnnotation(ClassId.topLevel(COMPOSABLE_SCOPE_FQN), context.session) -// != true -// ) -// return -// -// if (!isComposableScope) { -// if (handled.getOrPut(expression.source, ::mutableSetOf).add(ComposeInvestigatorErrors.ILLEGAL_COMPOSABLE_SCOPE_CALL)) -// reporter.reportOn(expression.source, ComposeInvestigatorErrors.ILLEGAL_COMPOSABLE_SCOPE_CALL, context) -// } + fun CheckerContext.isComposableScope(): Boolean = + when (val declaration = findClosest() ?: return false) { + is FirAnonymousFunction -> declaration.typeRef.coneType.functionTypeKind(session) === ComposableFunction + is FirSimpleFunction -> declaration.hasComposableAnnotation(session) + else -> unreachableBranch(declaration::class.simpleName) } - } } private object ComposableNameExpressionChecker : FirFunctionCallChecker(MppCheckerKind.Common) { diff --git a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableIntrinsicTransformer.kt b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableIntrinsicTransformer.kt index 9d270b4c..c3b335f1 100644 --- a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableIntrinsicTransformer.kt +++ b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableIntrinsicTransformer.kt @@ -62,6 +62,8 @@ public class InvalidationTraceTableIntrinsicTransformer( tableSymbol.getPropertyGetter(ComposableInvalidationTraceTable_CURRENT_COMPOSABLE_KEY_NAME.asString())!!.owner override fun visitCall(expression: IrCall): IrExpression { + // TODO generating `throw Exception(NO_TABLE)` code to the target IR instead + // of throwing it here if no table exists val table = tables[currentFile] return when (expression.symbol.owner.kotlinFqName) { currentTableGetterSymbol.kotlinFqName -> { diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_assert/DiagnosticsResultAsserter.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_assert/DiagnosticsResultAsserter.kt index ca7149f8..e4e4b103 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_assert/DiagnosticsResultAsserter.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_assert/DiagnosticsResultAsserter.kt @@ -13,9 +13,14 @@ import kotlin.test.fail import land.sungbin.composeinvestigator.compiler._compilation.FirAnalysisResult import org.jetbrains.kotlin.diagnostics.AbstractKtDiagnosticFactory +private const val SEPARATOR = "\n=====\n" + fun FirAnalysisResult.assertNoDiagnostic(diagnostic: AbstractKtDiagnosticFactory) { val results = diagnostics.getOrElse(diagnostic.name, ::emptyList) - assertTrue(results.isEmpty(), "Expected no diagnostic but found diagnostic(s).") + assertTrue( + results.isEmpty(), + "Expected no diagnostic but found diagnostics:\n${results.joinToString(SEPARATOR)}", + ) } fun FirAnalysisResult.assertDiagnostics( @@ -26,6 +31,6 @@ fun FirAnalysisResult.assertDiagnostics( if (results.isEmpty()) fail("Expected diagnostics message but no diagnostic was found.") - val actualMessages = results.joinToString("\n=====\n") + val actualMessages = results.joinToString(SEPARATOR) assertEquals(expectMessages().trim(), actualMessages) } diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableFunction.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableFunction.kt index 2f622faf..c208af8d 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableFunction.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableFunction.kt @@ -14,9 +14,8 @@ import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer @Composable private fun composableFunction() { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } + val t = currentComposableInvalidationTracer + t.currentComposableName + t.currentComposableName = ComposableName("") + currentComposableInvalidationTracer.currentComposableKeyName } diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableLambda.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableLambda.kt index 52b14fcc..d0157c72 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableLambda.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/composableLambda.kt @@ -10,17 +10,20 @@ package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage import androidx.compose.runtime.Composable +import land.sungbin.composeinvestigator.runtime.ComposableInvalidationTraceTable import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer -private fun composableLambda() { - l { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } +@Composable private fun composableLambda() { + val t = currentComposableInvalidationTracer + l(t) { + currentComposableName + it.currentComposableKeyName + currentComposableInvalidationTracer.currentComposableName = ComposableName("") } } -private fun l(l: @Composable () -> Unit) = Unit +private fun l( + t: ComposableInvalidationTraceTable, + b: @Composable ComposableInvalidationTraceTable.(ComposableInvalidationTraceTable) -> Unit, +) = Unit diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableFunction.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableFunction.kt index 0d1a114f..99743238 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableFunction.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableFunction.kt @@ -14,9 +14,8 @@ import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer @Composable private inline fun inlineComposableFunction() { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } + val t = currentComposableInvalidationTracer + t.currentComposableName + t.currentComposableName = ComposableName("") + currentComposableInvalidationTracer.currentComposableKeyName } diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableLambda.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableLambda.kt index 33d22072..565901fa 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableLambda.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineComposableLambda.kt @@ -10,17 +10,20 @@ package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage import androidx.compose.runtime.Composable +import land.sungbin.composeinvestigator.runtime.ComposableInvalidationTraceTable import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer -private fun inlineComposableLambda() { - l { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } +@Composable private inline fun inlineComposableLambda() { + val t = currentComposableInvalidationTracer + l(t) { + currentComposableName + it.currentComposableKeyName + currentComposableInvalidationTracer.currentComposableName = ComposableName("") } } -private inline fun l(b: @Composable () -> Unit) = Unit +private inline fun l( + t: ComposableInvalidationTraceTable, + b: @Composable ComposableInvalidationTraceTable.(ComposableInvalidationTraceTable) -> Unit, +) = Unit diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalFunction.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalFunction.kt index d927fc1b..e1aa18ce 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalFunction.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalFunction.kt @@ -13,9 +13,8 @@ import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer private inline fun inlineNormalFunction() { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } + val t = currentComposableInvalidationTracer + t.currentComposableName + t.currentComposableName = ComposableName("") + currentComposableInvalidationTracer.currentComposableKeyName } diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalLambda.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalLambda.kt index 9d196847..cc3e4cbd 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalLambda.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/inlineNormalLambda.kt @@ -9,17 +9,20 @@ package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage +import land.sungbin.composeinvestigator.runtime.ComposableInvalidationTraceTable import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer -private fun inlineNormalLambda() { - l { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } +private inline fun inlineNormalLambda() { + val t = currentComposableInvalidationTracer + l(t) { + currentComposableName + it.currentComposableKeyName + currentComposableInvalidationTracer.currentComposableName = ComposableName("") } } -private inline fun l(b: () -> Unit) = Unit +private inline fun l( + t: ComposableInvalidationTraceTable, + b: ComposableInvalidationTraceTable.(ComposableInvalidationTraceTable) -> Unit, +) = Unit diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalFunction.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalFunction.kt index e97a73e6..d70e7c70 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalFunction.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalFunction.kt @@ -13,9 +13,8 @@ import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer private fun normalFunction() { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } + val t = currentComposableInvalidationTracer + t.currentComposableName + t.currentComposableName = ComposableName("") + currentComposableInvalidationTracer.currentComposableKeyName } diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalLambda.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalLambda.kt index 03bdb9d5..2d661318 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalLambda.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/normalLambda.kt @@ -9,17 +9,20 @@ package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage +import land.sungbin.composeinvestigator.runtime.ComposableInvalidationTraceTable import land.sungbin.composeinvestigator.runtime.ComposableName import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer private fun normalLambda() { - l { - with(currentComposableInvalidationTracer) { - currentComposableName - currentComposableName = ComposableName("") - currentComposableKeyName - } + val t = currentComposableInvalidationTracer + l(t) { + currentComposableName + it.currentComposableKeyName + currentComposableInvalidationTracer.currentComposableName = ComposableName("") } } -private fun l(b: () -> Unit) = Unit +private fun l( + t: ComposableInvalidationTraceTable, + b: ComposableInvalidationTraceTable.(ComposableInvalidationTraceTable) -> Unit, +) = Unit diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithAllNoInvestigationComposables.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithAllNoInvestigationComposables.kt new file mode 100644 index 00000000..80930378 --- /dev/null +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithAllNoInvestigationComposables.kt @@ -0,0 +1,24 @@ +/* + * Developed by Ji Sungbin 2024. + * + * Licensed under the MIT. + * Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE + */ + +@file:Suppress("unused") + +package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage + +import androidx.compose.runtime.Composable +import land.sungbin.composeinvestigator.runtime.NoInvestigation +import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer + +private val t = currentComposableInvalidationTracer +private val a = Unit.run { + t.currentComposableName + t.currentComposableKeyName +} + +@Composable @NoInvestigation private fun C() {} +@Composable @NoInvestigation private fun C2() {} +@Composable @NoInvestigation private fun C3() {} diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithComposable.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithComposable.kt new file mode 100644 index 00000000..4cf774bf --- /dev/null +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithComposable.kt @@ -0,0 +1,21 @@ +/* + * Developed by Ji Sungbin 2024. + * + * Licensed under the MIT. + * Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE + */ + +@file:Suppress("unused") + +package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage + +import androidx.compose.runtime.Composable +import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer + +private val t = currentComposableInvalidationTracer +private val a = Unit.run { + t.currentComposableName + t.currentComposableKeyName +} + +@Composable private fun C() {} diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithNoComposable.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithNoComposable.kt new file mode 100644 index 00000000..925111d2 --- /dev/null +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/frontend/traceTableApiUsage/topLevelApiCallWithNoComposable.kt @@ -0,0 +1,18 @@ +/* + * Developed by Ji Sungbin 2024. + * + * Licensed under the MIT. + * Please see full license: https://github.com/jisungbin/ComposeInvestigator/blob/main/LICENSE + */ + +@file:Suppress("unused") + +package land.sungbin.composeinvestigator.compiler._source.frontend.traceTableApiUsage + +import land.sungbin.composeinvestigator.runtime.currentComposableInvalidationTracer + +private val t = currentComposableInvalidationTracer +private val a = Unit.run { + t.currentComposableName + t.currentComposableKeyName +} diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiUsageCheckerTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiUsageCheckerTest.kt index b7e29ef5..694004a1 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiUsageCheckerTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableApiUsageCheckerTest.kt @@ -13,32 +13,27 @@ import land.sungbin.composeinvestigator.compiler._assert.assertDiagnostics import land.sungbin.composeinvestigator.compiler._assert.assertNoDiagnostic import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest import land.sungbin.composeinvestigator.compiler.frontend.ComposeInvestigatorErrors.API_ACCESS_IN_NO_INVESTIGATION_FILE -import land.sungbin.composeinvestigator.compiler.frontend.ComposeInvestigatorErrors.ILLEGAL_COMPOSABLE_SCOPE_CALL -@Ignore("Need to rewrite logic to check if current function is in composable scope") +// TODO asserts ILLEGAL_COMPOSABLE_SCOPE_CALL diagnostic class InvalidationTraceTableApiUsageCheckerTest : AbstractCompilerTest(sourceRoot = "frontend/traceTableApiUsage") { @Test fun composableFunction() { val analyze = analyze(source("composableFunction.kt")) analyze.assertNoDiagnostic(API_ACCESS_IN_NO_INVESTIGATION_FILE) - analyze.assertNoDiagnostic(ILLEGAL_COMPOSABLE_SCOPE_CALL) } @Test fun composableLambda() { val analyze = analyze(source("composableLambda.kt")) analyze.assertNoDiagnostic(API_ACCESS_IN_NO_INVESTIGATION_FILE) - analyze.assertNoDiagnostic(ILLEGAL_COMPOSABLE_SCOPE_CALL) } @Test fun inlineComposableFunction() { val analyze = analyze(source("inlineComposableFunction.kt")) analyze.assertNoDiagnostic(API_ACCESS_IN_NO_INVESTIGATION_FILE) - analyze.assertNoDiagnostic(ILLEGAL_COMPOSABLE_SCOPE_CALL) } @Test fun inlineComposableLambda() { val analyze = analyze(source("inlineComposableLambda.kt")) analyze.assertNoDiagnostic(API_ACCESS_IN_NO_INVESTIGATION_FILE) - analyze.assertNoDiagnostic(ILLEGAL_COMPOSABLE_SCOPE_CALL) } @Test fun inlineNormalFunction() { @@ -46,35 +41,12 @@ class InvalidationTraceTableApiUsageCheckerTest : AbstractCompilerTest(sourceRoo analyze.assertDiagnostics(API_ACCESS_IN_NO_INVESTIGATION_FILE) { """ error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - with(currentComposableInvalidationTracer) { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + val t = currentComposableInvalidationTracer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ===== error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ - """ - } - analyze.assertDiagnostics(ILLEGAL_COMPOSABLE_SCOPE_CALL) { - """ -error: @ComposableScope API can only be used in a Composable function. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ + currentComposableInvalidationTracer.currentComposableKeyName + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """ } } @@ -84,35 +56,12 @@ error: @ComposableScope API can only be used in a Composable function. analyze.assertDiagnostics(API_ACCESS_IN_NO_INVESTIGATION_FILE) { """ error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - with(currentComposableInvalidationTracer) { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + val t = currentComposableInvalidationTracer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ===== error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ - """ - } - analyze.assertDiagnostics(ILLEGAL_COMPOSABLE_SCOPE_CALL) { - """ -error: @ComposableScope API can only be used in a Composable function. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ + currentComposableInvalidationTracer.currentComposableName = ComposableName("") + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """ } } @@ -122,35 +71,12 @@ error: @ComposableScope API can only be used in a Composable function. analyze.assertDiagnostics(API_ACCESS_IN_NO_INVESTIGATION_FILE) { """ error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - with(currentComposableInvalidationTracer) { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + val t = currentComposableInvalidationTracer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ===== error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ - """ - } - analyze.assertDiagnostics(ILLEGAL_COMPOSABLE_SCOPE_CALL) { - """ -error: @ComposableScope API can only be used in a Composable function. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ + currentComposableInvalidationTracer.currentComposableKeyName + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """ } } @@ -160,35 +86,40 @@ error: @ComposableScope API can only be used in a Composable function. analyze.assertDiagnostics(API_ACCESS_IN_NO_INVESTIGATION_FILE) { """ error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - with(currentComposableInvalidationTracer) { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -===== -error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ + val t = currentComposableInvalidationTracer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ===== error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== + currentComposableInvalidationTracer.currentComposableName = ComposableName("") + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + """ + } + } + + @Test fun topLevelApiCallWithComposable() { + val analyze = analyze(source("topLevelApiCallWithComposable.kt")) + analyze.assertNoDiagnostic(API_ACCESS_IN_NO_INVESTIGATION_FILE) + } + + @Test fun topLevelApiCallWithNoComposable() { + val analyze = analyze(source("topLevelApiCallWithNoComposable.kt")) + analyze.assertDiagnostics(API_ACCESS_IN_NO_INVESTIGATION_FILE) { + """ error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ +private val t = currentComposableInvalidationTracer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """ } - analyze.assertDiagnostics(ILLEGAL_COMPOSABLE_SCOPE_CALL) { + } + + @Ignore("NoComposableFileChecker should run first, but FirExtension has no guaranteed execution order?") + @Test fun topLevelApiCallWithAllNoInvestigationComposables() { + val analyze = analyze(source("topLevelApiCallWithAllNoInvestigationComposables.kt")) + analyze.assertDiagnostics(API_ACCESS_IN_NO_INVESTIGATION_FILE) { """ -error: @ComposableScope API can only be used in a Composable function. - currentComposableName - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableName = ComposableName("") - ^^^^^^^^^^^^^^^^^^^^^ -===== -error: @ComposableScope API can only be used in a Composable function. - currentComposableKeyName - ^^^^^^^^^^^^^^^^^^^^^^^^ +error: files that are '@file:NoInvestigation' or does not contain any Composables will not generate a ComposableInvalidationTraceTable. +private val t = currentComposableInvalidationTracer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """ } } diff --git a/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableInvalidationTraceTable.kt b/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableInvalidationTraceTable.kt index c7f519a2..5e88bd3b 100644 --- a/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableInvalidationTraceTable.kt +++ b/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableInvalidationTraceTable.kt @@ -79,12 +79,11 @@ public class ComposableInvalidationTraceTable @ComposeInvestigatorCompilerApi pu * If you call this getter from a Composable configured as an anonymous function, it will always * be named 'anonymous.' Therefore, in this case, we recommend you specify your Composable name. */ - @property:ComposableScope public var currentComposableName: ComposableName - @[Stable JvmSynthetic] get() { + @[Stable ComposableScope JvmSynthetic] get() { throw IntrinsicImplementedError() } - @JvmSynthetic set(@Suppress("unused") name) { + @[ComposableScope JvmSynthetic] set(@Suppress("unused") name) { throw IntrinsicImplementedError() } @@ -126,7 +125,7 @@ public class ComposableInvalidationTraceTable @ComposeInvestigatorCompilerApi pu * found by ComposeInvestigator. If not found, returns `null`. */ // TODO is this operation really O(1)? - // When resolved, add this note into KDoc: *Note: This operation takes `O(1)`*. + // When become clear, add this note into KDoc: *Note: This operation takes `O(1)`*. public fun findStateObjectName(value: Any): String? = stateObjectMap[value] /** @suppress ComposeInvestigator compiler-only API */ diff --git a/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableScope.kt b/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableScope.kt index 4d6b2629..9073bce0 100644 --- a/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableScope.kt +++ b/runtime/src/main/kotlin/land/sungbin/composeinvestigator/runtime/ComposableScope.kt @@ -10,5 +10,5 @@ package land.sungbin.composeinvestigator.runtime /** APIs annotated with this should only be used within Composable functions. */ @MustBeDocumented @Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) public annotation class ComposableScope