From ddc5fd661fca93918dc5d8d2073dc05aaa9b98e0 Mon Sep 17 00:00:00 2001 From: Ji Sungbin Date: Wed, 25 Sep 2024 20:51:01 +0900 Subject: [PATCH 1/4] Rewrite NoComposableFileChecker logics --- ...idationTraceTableInstantiationValidator.kt | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidator.kt b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidator.kt index 134f8e29..2f968bec 100644 --- a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidator.kt +++ b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidator.kt @@ -8,6 +8,7 @@ package land.sungbin.composeinvestigator.compiler.frontend import androidx.compose.compiler.plugins.kotlin.k2.hasComposableAnnotation +import androidx.compose.compiler.plugins.kotlin.k2.isComposable import androidx.compose.compiler.plugins.kotlin.lower.fastForEach import land.sungbin.composeinvestigator.compiler.NO_INVESTIGATION_FQN import land.sungbin.composeinvestigator.compiler.lower.unsafeLazy @@ -21,11 +22,13 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationChecker import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFileChecker import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.hasAnnotation import org.jetbrains.kotlin.fir.declarations.validate import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotation import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyAnnotationArgumentMapping -import org.jetbrains.kotlin.fir.references.toResolvedFunctionSymbol +import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol import org.jetbrains.kotlin.fir.smartPlus import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef import org.jetbrains.kotlin.fir.types.constructClassLikeType @@ -39,13 +42,15 @@ public class InvalidationTraceTableInstantiationValidator(session: FirSession) : } private object NoComposableFileChecker : FirFileChecker(MppCheckerKind.Common) { + private val NO_INVESTIGATION = ClassId.topLevel(NO_INVESTIGATION_FQN) private val noInvestigationType by unsafeLazy { buildResolvedTypeRef { - coneType = ClassId.topLevel(NO_INVESTIGATION_FQN).constructClassLikeType() + coneType = NO_INVESTIGATION.constructClassLikeType() } } override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) { + if (declaration.hasAnnotation(NO_INVESTIGATION, context.session)) return var hasComposable = false val composableCallVisitor = object : FirDefaultVisitorVoid() { @@ -54,17 +59,25 @@ private object NoComposableFileChecker : FirFileChecker(MppCheckerKind.Common) { element.acceptChildren(this) } - override fun visitFunctionCall(functionCall: FirFunctionCall) { - if (functionCall.calleeReference.toResolvedFunctionSymbol()!!.hasComposableAnnotation(context.session)) + override fun visitFunctionCall(call: FirFunctionCall) { + if (call.calleeReference.toResolvedCallableSymbol()!!.isComposable(context.session)) hasComposable = true if (hasComposable) return - super.visitFunctionCall(functionCall) + + super.visitFunctionCall(call) } } declaration.declarations.fastForEach { element -> - element.acceptChildren(composableCallVisitor) + // fast path -- 1 + if (element.hasComposableAnnotation(context.session)) + return@check // early return if the file has composable functions + + if (element is FirFunction) // fast path -- 2 + element.body?.acceptChildren(composableCallVisitor) + else // slow path + element.acceptChildren(composableCallVisitor) } if (hasComposable) return From bf2a110c9f6f358b72a47a96480c9ebb21f327a2 Mon Sep 17 00:00:00 2001 From: Ji Sungbin Date: Wed, 25 Sep 2024 22:02:26 +0900 Subject: [PATCH 2/4] Fix tests --- .../_compilation/AbstractCompilerTest.kt | 230 ++++++++-- .../compiler/_source/utils.kt | 8 - .../DurableComposableKeyAnalyzerTest.kt | 8 +- .../ComposableNameExpressionCheckerTest.kt | 7 +- ...validationTraceTableApiUsageCheckerTest.kt | 19 +- ...ionTraceTableInstantiationValidatorTest.kt | 9 +- .../lower/InvalidationProcessTransformTest.kt | 405 +++++++++++++++++- .../lower/InvalidationSkipTransformTest.kt | 351 ++++++++++++++- .../lower/InvalidationTraceTableCallTest.kt | 2 + .../InvalidationTraceTableInstantiateTest.kt | 2 + .../lower/StateInitializerTransformTest.kt | 2 + 11 files changed, 960 insertions(+), 83 deletions(-) diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_compilation/AbstractCompilerTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_compilation/AbstractCompilerTest.kt index c59c64f4..bef40a28 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_compilation/AbstractCompilerTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_compilation/AbstractCompilerTest.kt @@ -18,6 +18,8 @@ import land.sungbin.composeinvestigator.compiler.ComposeInvestigatorFirExtension import land.sungbin.composeinvestigator.compiler.ComposeInvestigatorFirstPhaseExtension import land.sungbin.composeinvestigator.compiler.ComposeInvestigatorLastPhaseExtension import land.sungbin.composeinvestigator.compiler.FeatureFlag +import land.sungbin.composeinvestigator.compiler._source.sourcePath +import land.sungbin.composeinvestigator.compiler._source.sourceString import land.sungbin.composeinvestigator.runtime.ComposeInvestigatorConfig import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots @@ -38,41 +40,12 @@ import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.config.messageCollector import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.jetbrains.kotlin.fir.pipeline.Fir2IrActualizedResult +import org.jetbrains.kotlin.ir.declarations.IrFile abstract class AbstractCompilerTest( - private val features: EnumSet = EnumSet.noneOf(FeatureFlag::class.java), + private val features: EnumSet = NO_FEATURES, + private val sourceRoot: String? = null, ) { - companion object { - private fun File.applyExistenceCheck(): File = apply { - if (!exists()) throw NoSuchFileException(this) - } - - private val homeDir: String = run { - val userDir = System.getProperty("user.dir") - val dir = File(userDir ?: ".") - val path = FileUtil.toCanonicalPath(dir.absolutePath) - File(path).applyExistenceCheck().absolutePath - } - - // https://github.com/JetBrains/kotlin/blob/bb25d2f8aa74406ff0af254b2388fd601525386a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCompilerTest.kt#L212-L228 - val defaultClassPath by lazy { - listOf( - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - jarFor(), - ) - } - } - private val disposable = Disposer.newDisposable() @BeforeTest fun setSystemProperties() { @@ -146,12 +119,197 @@ abstract class AbstractCompilerTest( createK2Compiler().compile(file) protected fun irTest(file: SourceFile, expect: () -> String) { - val actual = createK2Compiler().compile(file) - .irModuleFragment.files - .single() + val actual = createK2Compiler().compile(file).irModuleFragment.files.single() + assertEquals(expect().trim(), with(GoldenUtil) { actual.dumpSrcForGolden(code = file.source) }) + } + + protected fun source(filename: String): SourceFile { + val resolvedFilename = sourceRoot?.let { "$it/$filename" } ?: filename + return SourceFile( + name = resolvedFilename.substringAfterLast('/'), + source = sourceString(resolvedFilename), + path = sourcePath(resolvedFilename).substringBeforeLast('/'), + ) + } + + companion object { + private fun File.applyExistenceCheck(): File = apply { + if (!exists()) throw NoSuchFileException(this) + } + + private val homeDir: String = run { + val userDir = System.getProperty("user.dir") + val dir = File(userDir ?: ".") + val path = FileUtil.toCanonicalPath(dir.absolutePath) + File(path).applyExistenceCheck().absolutePath + } + + private val NO_FEATURES = EnumSet.noneOf(FeatureFlag::class.java) + + // https://github.com/JetBrains/kotlin/blob/bb25d2f8aa74406ff0af254b2388fd601525386a/plugins/compose/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/AbstractCompilerTest.kt#L212-L228 + private val defaultClassPath by lazy { + listOf( + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + jarFor(), + ) + } + } +} + +private object GoldenUtil { + private val MatchResult.text get() = groupValues[0] + private fun MatchResult.number() = groupValues[1].toInt() + private fun MatchResult.isChar(c: String) = text == c + private fun MatchResult.isNumber() = groupValues[1].isNotEmpty() + private fun MatchResult.isFileName() = groups[4] != null + + fun IrFile.dumpSrcForGolden(code: String): String { + val keySet = mutableListOf() + return this .dumpSrc(useFir = true) - assertEquals(expect().trim(), actual) + .replace('$', '%') + // replace source keys for start group calls + .replace(Regex("(%composer\\.start(Restart|Movable|Replaceable|Replace)Group\\()-?((0b)?[-\\d]+)")) { match -> + val stringKey = match.groupValues[3] + val key = if (stringKey.startsWith("0b")) Integer.parseInt(stringKey.drop(2), 2) else stringKey.toInt() + if (key in keySet) { + "${match.groupValues[1]}" + } else { + keySet.add(key) + "${match.groupValues[1]}<>" + } + } + .replace(Regex("(sourceInformationMarkerStart\\(%composer, )([-\\d]+)")) { match -> + "${match.groupValues[1]}<>" + } + // replace traceEventStart values with a token + .replace(Regex("traceEventStart\\(-?\\d+, (%dirty|%changed|-1), (%dirty1|%changed1|-1), (.*)")) { match -> + "traceEventStart(<>, ${match.groupValues[1]}, ${match.groupValues[2]}, <>)" + } + // replace source information with source it references + .replace(Regex("(%composer\\.start(Restart|Movable|Replaceable|Replace)Group\\([^\"\\n]*)\"(.*)\"\\)")) { match -> + "${match.groupValues[1]}\"${generateSourceInfo(match.groupValues[4], code)}\")" + } + .replace(Regex("(sourceInformation(MarkerStart)?\\(.*)\"(.*)\"\\)")) { match -> + "${match.groupValues[1]}\"${generateSourceInfo(match.groupValues[3], code)}\")" + } + .replace(Regex("(composableLambda[N]?\\([^\"\\n]*)\"(.*)\"\\)")) { match -> + "${match.groupValues[1]}\"${generateSourceInfo(match.groupValues[2], code)}\")" + } + .replace(Regex("(rememberComposableLambda[N]?)\\((-?\\d+)")) { match -> + "${match.groupValues[1]}(<>" + } + // replace source keys for joinKey calls + .replace(Regex("(%composer\\.joinKey\\()([-\\d]+)")) { match -> + "${match.groupValues[1]}<>" + } + // composableLambdaInstance(<>, true) + .replace(Regex("(composableLambdaInstance\\()([-\\d]+, (true|false))")) { match -> + "${match.groupValues[1]}<>, ${match.groupValues[3]}" + } + // composableLambda(%composer, <>, true) + .replace(Regex("(composableLambda\\(%composer,\\s)([-\\d]+)")) { match -> + "${match.groupValues[1]}<>" + } + .trimIndent() + .trimTrailingWhitespaces() + } + + @Suppress("RegExpSimplifiable") + private fun generateSourceInfo(sourceInfo: String, source: String): String { + val regex = Regex("(\\d+)|([,])|([*])|([:])|C(\\(.*\\))?|L|(P\\(*\\))|@") + + var current = 0 + var currentResult = regex.find(sourceInfo, current) + var result = "" + + fun next(): MatchResult? { + currentResult?.let { match -> + current = match.range.last + 1 + currentResult = match.next() + } + return currentResult + } + + // A location has the format: []['@' ['L' ]] + // where the named productions are numbers + fun parseLocation(): String? { + var mr = currentResult + if (mr != null && mr.isNumber()) { + // line number, we ignore the value in during testing. + mr = next() + } + if (mr != null && mr.isChar("@")) { + // Offset + mr = next() + if (mr == null || !mr.isNumber()) { + return null + } + val offset = mr.number() + mr = next() + var ellipsis = "" + val maxFragment = 6 + val rawLength = if (mr != null && mr.isChar("L")) { + mr = next() + if (mr == null || !mr.isNumber()) { + return null + } + mr.number().also { next() } + } else { + maxFragment + } + val eol = source.indexOf('\n', offset).let { + if (it < 0) source.length else it + } + val space = source.indexOf(' ', offset).let { + if (it < 0) source.length else it + } + val maxEnd = offset + maxFragment + if (eol > maxEnd && space > maxEnd) ellipsis = "..." + val length = minOf(maxEnd, minOf(offset + rawLength, space, eol)) - offset + return "<${source.substring(offset, offset + length)}$ellipsis>" + } + return null + } + + while (currentResult != null) { + val mr = currentResult!! + if (mr.range.first != current) { + return "invalid source info at $current: '$sourceInfo'" + } + when { + mr.isNumber() || mr.isChar("@") -> { + val fragment = parseLocation() ?: return "invalid source info at $current: '$sourceInfo'" + result += fragment + } + mr.isFileName() -> { + return result + ":" + sourceInfo.substring(mr.range.last + 1) + } + else -> { + result += mr.text + next() + } + } + require(mr != currentResult) { "regex didn't advance" } + } + + if (current != sourceInfo.length) return "invalid source info at $current: '$sourceInfo'" + + return result } + + private fun String.trimTrailingWhitespaces(): String = + split('\n').joinToString("\n", transform = String::trimEnd) } private inline fun jarFor() = File(PathUtil.getJarPathForClass(T::class.java)) diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/utils.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/utils.kt index 95bd04b6..40f7dd34 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/utils.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/utils.kt @@ -8,14 +8,6 @@ package land.sungbin.composeinvestigator.compiler._source import java.io.File -import land.sungbin.composeinvestigator.compiler._compilation.SourceFile - -fun source(filename: String): SourceFile = - SourceFile( - name = filename.substringAfterLast('/'), - source = sourceString(filename), - path = sourcePath(filename).substringBeforeLast('/'), - ) fun sourcePath(filename: String): String = "src/test/kotlin/land/sungbin/composeinvestigator/compiler/_source/$filename" diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/analysis/DurableComposableKeyAnalyzerTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/analysis/DurableComposableKeyAnalyzerTest.kt index 46b263ed..fe7b92af 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/analysis/DurableComposableKeyAnalyzerTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/analysis/DurableComposableKeyAnalyzerTest.kt @@ -12,16 +12,18 @@ import kotlin.test.Test import kotlin.test.assertEquals import land.sungbin.composeinvestigator.compiler.FeatureFlag import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest -import land.sungbin.composeinvestigator.compiler._source.source import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrConstructorCall import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf import org.jetbrains.kotlin.utils.addToStdlib.safeAs -class DurableComposableKeyAnalyzerTest : AbstractCompilerTest(enumSetOf(FeatureFlag.InvalidationProcessTracing)) { +class DurableComposableKeyAnalyzerTest : AbstractCompilerTest( + enumSetOf(FeatureFlag.InvalidationProcessTracing), + sourceRoot = "analysis/durableComposableKey", +) { @Test fun generates_a_unique_key_for_the_same_function_name() { - val result = compile(source("analysis/durableComposableKey/sameFunctionNames.kt")) + val result = compile(source("sameFunctionNames.kt")) val trace = result.pluginContext.irTrace val functions = result.irModuleFragment diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/ComposableNameExpressionCheckerTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/ComposableNameExpressionCheckerTest.kt index e4de3fbe..eaf7c9db 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/ComposableNameExpressionCheckerTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/ComposableNameExpressionCheckerTest.kt @@ -11,17 +11,16 @@ import kotlin.test.Test 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._source.source import land.sungbin.composeinvestigator.compiler.frontend.ComposeInvestigatorErrors.UNSUPPORTED_COMPOSABLE_NAME -class ComposableNameExpressionCheckerTest : AbstractCompilerTest() { +class ComposableNameExpressionCheckerTest : AbstractCompilerTest(sourceRoot = "frontend/composableNameExpression") { @Test fun stringHardcodeExpression() { - val analyze = analyze(source("frontend/composableNameExpression/stringHardcodeExpression.kt")) + val analyze = analyze(source("stringHardcodeExpression.kt")) analyze.assertNoDiagnostic(UNSUPPORTED_COMPOSABLE_NAME) } @Test fun magicNumberToStringExpression() { - val analyze = analyze(source("frontend/composableNameExpression/magicNumberToStringExpression.kt")) + val analyze = analyze(source("magicNumberToStringExpression.kt")) analyze.assertDiagnostics(UNSUPPORTED_COMPOSABLE_NAME) { """ error: currently, only string hardcodes are supported as arguments to ComposableName. 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 eeaeae83..b7e29ef5 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 @@ -12,38 +12,37 @@ import kotlin.test.Test 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._source.source 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") -class InvalidationTraceTableApiUsageCheckerTest : AbstractCompilerTest() { +class InvalidationTraceTableApiUsageCheckerTest : AbstractCompilerTest(sourceRoot = "frontend/traceTableApiUsage") { @Test fun composableFunction() { - val analyze = analyze(source("frontend/traceTableApiUsage/composableFunction.kt")) + 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("frontend/traceTableApiUsage/composableLambda.kt")) + 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("frontend/traceTableApiUsage/inlineComposableFunction.kt")) + 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("frontend/traceTableApiUsage/inlineComposableLambda.kt")) + val analyze = analyze(source("inlineComposableLambda.kt")) analyze.assertNoDiagnostic(API_ACCESS_IN_NO_INVESTIGATION_FILE) analyze.assertNoDiagnostic(ILLEGAL_COMPOSABLE_SCOPE_CALL) } @Test fun inlineNormalFunction() { - val analyze = analyze(source("frontend/traceTableApiUsage/inlineNormalFunction.kt")) + val analyze = analyze(source("inlineNormalFunction.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. @@ -81,7 +80,7 @@ error: @ComposableScope API can only be used in a Composable function. } @Test fun inlineNormalLambda() { - val analyze = analyze(source("frontend/traceTableApiUsage/inlineNormalLambda.kt")) + val analyze = analyze(source("inlineNormalLambda.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. @@ -119,7 +118,7 @@ error: @ComposableScope API can only be used in a Composable function. } @Test fun normalFunction() { - val analyze = analyze(source("frontend/traceTableApiUsage/normalFunction.kt")) + val analyze = analyze(source("normalFunction.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. @@ -157,7 +156,7 @@ error: @ComposableScope API can only be used in a Composable function. } @Test fun normalLambda() { - val analyze = analyze(source("frontend/traceTableApiUsage/normalLambda.kt")) + val analyze = analyze(source("normalLambda.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. diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidatorTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidatorTest.kt index f638db87..80b37c32 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidatorTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/frontend/InvalidationTraceTableInstantiationValidatorTest.kt @@ -13,27 +13,26 @@ import kotlin.test.assertContains import kotlin.test.assertFalse import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest import land.sungbin.composeinvestigator.compiler._compilation.FirAnalysisResult -import land.sungbin.composeinvestigator.compiler._source.source import land.sungbin.composeinvestigator.runtime.NoInvestigation import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.coneType @Ignore("Need to reimplementation") -class InvalidationTraceTableInstantiationValidatorTest : AbstractCompilerTest() { +class InvalidationTraceTableInstantiationValidatorTest : AbstractCompilerTest(sourceRoot = "frontend/traceTableInstantiation") { @Test fun noneComposable() { - val analyze = analyze(source("frontend/traceTableInstantiation/noneComposable.kt")) + val analyze = analyze(source("noneComposable.kt")) assertContains(analyze.fileAnnotations(), NoInvestigation::class.qualifiedName!!) } @Test fun singleComposableFunction() { - val analyze = analyze(source("frontend/traceTableInstantiation/singleComposableFunction.kt")) + val analyze = analyze(source("singleComposableFunction.kt")) // TODO assertNotContains (KT-53336) assertFalse(analyze.fileAnnotations().contains(NoInvestigation::class.qualifiedName!!)) } @Test fun singleComposableLambda() { - val analyze = analyze(source("frontend/traceTableInstantiation/singleComposableLambda.kt")) + val analyze = analyze(source("singleComposableLambda.kt")) // TODO assertNotContains (KT-53336) assertFalse(analyze.fileAnnotations().contains(NoInvestigation::class.qualifiedName!!)) diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationProcessTransformTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationProcessTransformTest.kt index e7ab72e9..9febcd22 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationProcessTransformTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationProcessTransformTest.kt @@ -7,52 +7,425 @@ package land.sungbin.composeinvestigator.compiler.lower +import kotlin.test.Ignore import kotlin.test.Test import land.sungbin.composeinvestigator.compiler.FeatureFlag import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest -import land.sungbin.composeinvestigator.compiler._source.source import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf -class InvalidationProcessTransformTest : AbstractCompilerTest(enumSetOf(FeatureFlag.InvalidationProcessTracing)) { - @Test fun movableComposable() = irTest(source("lower/invalidationProcessAndSkip/movableComposable.kt")) { +@Ignore( + "" + + "- Composable properties should also be tested.\n" + + "- Stable Expression Composable should also be tested.", +) +class InvalidationProcessTransformTest : AbstractCompilerTest( + enumSetOf(FeatureFlag.InvalidationProcessTracing), + sourceRoot = "lower/invalidationProcessAndSkip", +) { + @Test fun movableComposable() = irTest(source("movableComposable.kt")) { """ - +val ComposableInvalidationTraceTableImpl%MovableComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%MovableComposableKt.computeInvalidationReason("fun-blockComposable(Any,Any)Unit/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-movableComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "movableComposable.kt"), Processed(tmp3_invalidationReason)) + %composer.startMovableGroup(<>, %composer.joinKey(any, any2)) + use(any) + val tmp0 = use(any2) + %composer.endMovableGroup() + tmp0 + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%MovableComposableKt.computeInvalidationReason("fun-expressionComposable(Any,Any)Int/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-movableComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("expressionComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "movableComposable.kt"), Processed(tmp3_invalidationReason)) + val tmp0 = { + %composer.startMovableGroup(<>, %composer.joinKey(any, any2)) + val tmp1 = use(any) + use(any2) + %composer.endMovableGroup() + tmp1 + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} """ } - @Test fun noGroupComposable() = irTest(source("lower/invalidationProcessAndSkip/noGroupComposable.kt")) { + @Test fun noGroupComposable() = irTest(source("noGroupComposable.kt")) { """ - +val ComposableInvalidationTraceTableImpl%NoGroupComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@ExplicitGroupsComposable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%NoGroupComposableKt.computeInvalidationReason("fun-blockComposable(Any,Any)Unit/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-noGroupComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "noGroupComposable.kt"), Processed(tmp3_invalidationReason)) + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } +} +@Composable +@ExplicitGroupsComposable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%NoGroupComposableKt.computeInvalidationReason("fun-expressionComposable(Any,Any)Int/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-noGroupComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("expressionComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "noGroupComposable.kt"), Processed(tmp3_invalidationReason)) + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + return tmp0 +} """ } - @Test fun noInvestigationComposable() = irTest(source("lower/invalidationProcessAndSkip/noInvestigationComposable.kt")) { + @Test fun noInvestigationComposable() = irTest(source("noInvestigationComposable.kt")) { """ - +val ComposableInvalidationTraceTableImpl%NoInvestigationComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@NoInvestigation +private fun noInvestigationBlockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + noInvestigationBlockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +@NoInvestigation +private fun noInvestigationExpressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} """ } - @Test fun noInvestigationFile() = irTest(source("lower/invalidationProcessAndSkip/noInvestigationFile.kt")) { + // FIXME file-level annotations are not rendered by dumpSrc + @Test fun noInvestigationFile() = irTest(source("noInvestigationFile.kt")) { """ - +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} """ } - @Test fun readonlyComposable() = irTest(source("lower/invalidationProcessAndSkip/readonlyComposable.kt")) { + @Test fun readonlyComposable() = irTest(source("readonlyComposable.kt")) { """ - +val ComposableInvalidationTraceTableImpl%ReadonlyComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@ReadOnlyComposable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%ReadonlyComposableKt.computeInvalidationReason("fun-blockComposable(Any,Any)Unit/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-readonlyComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "readonlyComposable.kt"), Processed(tmp3_invalidationReason)) + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +@ReadOnlyComposable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%ReadonlyComposableKt.computeInvalidationReason("fun-expressionComposable(Any,Any)Int/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-readonlyComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("expressionComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "readonlyComposable.kt"), Processed(tmp3_invalidationReason)) + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + return tmp0 +} """ } - @Test fun replaceableComposable() = irTest(source("lower/invalidationProcessAndSkip/replaceableComposable.kt")) { + @Test fun replaceableComposable() = irTest(source("replaceableComposable.kt")) { """ - +val ComposableInvalidationTraceTableImpl%ReplaceableComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@NonRestartableComposable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%ReplaceableComposableKt.computeInvalidationReason("fun-blockComposable(Any,Any)Unit/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-replaceableComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "replaceableComposable.kt"), Processed(tmp3_invalidationReason)) + val tmp0_subject = Default.nextBoolean() + when { + tmp0_subject == true -> { + use(any) + } + else -> { + use(any2) + } + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() +} +@Composable +@NonRestartableComposable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%ReplaceableComposableKt.computeInvalidationReason("fun-expressionComposable(Any,Any)Int/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-replaceableComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("expressionComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "replaceableComposable.kt"), Processed(tmp3_invalidationReason)) + val tmp0 = { + val tmp0_subject = Default.nextBoolean() + when { + tmp0_subject == true -> { + use(any) + } + else -> { + use(any2) + } + } + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} """ } - @Test fun restartableComposable() = irTest(source("lower/invalidationProcessAndSkip/restartableComposable.kt")) { + @Test fun restartableComposable() = irTest(source("restartableComposable.kt")) { """ - +val ComposableInvalidationTraceTableImpl%RestartableComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%RestartableComposableKt.computeInvalidationReason("fun-blockComposable(Any,Any)Unit/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-restartableComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "restartableComposable.kt"), Processed(tmp3_invalidationReason)) + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_currentValueArguments = mutableListOf() + val tmp1_any%valueArgu = ValueArgument("any", "kotlin.Any", any.toString(), any.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp1_any%valueArgu) + val tmp2_any2%valueArgu = ValueArgument("any2", "kotlin.Any", any2.toString(), any2.hashCode(), Certain(false)) + tmp0_currentValueArguments.add(tmp2_any2%valueArgu) + val tmp3_invalidationReason = ComposableInvalidationTraceTableImpl%RestartableComposableKt.computeInvalidationReason("fun-expressionComposable(Any,Any)Int/pkg-land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip/file-restartableComposable.kt", tmp0_currentValueArguments) + ComposeInvestigatorConfig.logger.log(ComposableInformation("expressionComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "restartableComposable.kt"), Processed(tmp3_invalidationReason)) + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} """ } } diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationSkipTransformTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationSkipTransformTest.kt index 11689a15..d798eeb1 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationSkipTransformTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationSkipTransformTest.kt @@ -7,8 +7,357 @@ package land.sungbin.composeinvestigator.compiler.lower +import kotlin.test.Ignore +import kotlin.test.Test import land.sungbin.composeinvestigator.compiler.FeatureFlag import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf -class InvalidationSkipTransformTest : AbstractCompilerTest(enumSetOf(FeatureFlag.InvalidationSkipTracing)) +@Ignore( + "" + + "- Composable properties should also be tested.\n" + + "- Stable Expression Composable should also be tested.", +) +class InvalidationSkipTransformTest : AbstractCompilerTest( + enumSetOf(FeatureFlag.InvalidationSkipTracing), + sourceRoot = "lower/invalidationProcessAndSkip", +) { + @Test fun movableComposable() = irTest(source("movableComposable.kt")) { + """ +val ComposableInvalidationTraceTableImpl%MovableComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %composer.startMovableGroup(<>, %composer.joinKey(any, any2)) + use(any) + val tmp0 = use(any2) + %composer.endMovableGroup() + tmp0 + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "movableComposable.kt"), Skipped) + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = { + %composer.startMovableGroup(<>, %composer.joinKey(any, any2)) + val tmp1 = use(any) + use(any2) + %composer.endMovableGroup() + tmp1 + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} + """ + } + + @Test fun noGroupComposable() = irTest(source("noGroupComposable.kt")) { + """ +val ComposableInvalidationTraceTableImpl%NoGroupComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@ExplicitGroupsComposable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } +} +@Composable +@ExplicitGroupsComposable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + return tmp0 +} + """ + } + + @Test fun noInvestigationComposable() = irTest(source("noInvestigationComposable.kt")) { + """ +val ComposableInvalidationTraceTableImpl%NoInvestigationComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@NoInvestigation +private fun noInvestigationBlockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + noInvestigationBlockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +@NoInvestigation +private fun noInvestigationExpressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} + """ + } + + @Test fun noInvestigationFile() = irTest(source("noInvestigationFile.kt")) { + """ +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} + """ + } + + @Test fun readonlyComposable() = irTest(source("readonlyComposable.kt")) { + """ +val ComposableInvalidationTraceTableImpl%ReadonlyComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@ReadOnlyComposable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "readonlyComposable.kt"), Skipped) + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +@ReadOnlyComposable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + return tmp0 +} + """ + } + + @Test fun replaceableComposable() = irTest(source("replaceableComposable.kt")) { + """ +val ComposableInvalidationTraceTableImpl%ReplaceableComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +@NonRestartableComposable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0_subject = Default.nextBoolean() + when { + tmp0_subject == true -> { + use(any) + } + else -> { + use(any2) + } + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() +} +@Composable +@NonRestartableComposable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = { + val tmp0_subject = Default.nextBoolean() + when { + tmp0_subject == true -> { + use(any) + } + else -> { + use(any2) + } + } + } + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} + """ + } + + @Test fun restartableComposable() = irTest(source("restartableComposable.kt")) { + """ +val ComposableInvalidationTraceTableImpl%RestartableComposableKt: ComposableInvalidationTraceTable = ComposableInvalidationTraceTable() +private fun use(any: Any): Int { + return any.hashCode() +} +@Composable +private fun blockComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + val %dirty = %changed + if (%changed and 0b0110 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any)) 0b0100 else 0b0010 + } + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(any2)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010011 != 0b00010010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + ComposeInvestigatorConfig.logger.log(ComposableInformation("blockComposable", "land.sungbin.composeinvestigator.compiler._source.lower.invalidationProcessAndSkip", "restartableComposable.kt"), Skipped) + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + blockComposable(any, any2, %composer, updateChangedFlags(%changed or 0b0001)) + } +} +@Composable +private fun expressionComposable(any: Any, any2: Any, %composer: Composer?, %changed: Int): Int { + %composer.startReplaceGroup(<>) + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + val tmp0 = use(any) + use(any2) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endReplaceGroup() + return tmp0 +} + """ + } +} diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableCallTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableCallTest.kt index da4715e9..307bd96c 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableCallTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableCallTest.kt @@ -7,8 +7,10 @@ package land.sungbin.composeinvestigator.compiler.lower +import kotlin.test.Ignore import land.sungbin.composeinvestigator.compiler.FeatureFlag import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf +@Ignore("TODO") class InvalidationTraceTableCallTest : AbstractCompilerTest(enumSetOf(FeatureFlag.InvalidationTraceTableIntrinsicCall)) diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableInstantiateTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableInstantiateTest.kt index 65cef3af..ac803dfc 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableInstantiateTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/InvalidationTraceTableInstantiateTest.kt @@ -7,8 +7,10 @@ package land.sungbin.composeinvestigator.compiler.lower +import kotlin.test.Ignore import land.sungbin.composeinvestigator.compiler.FeatureFlag import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf +@Ignore("TODO") class InvalidationTraceTableInstantiateTest : AbstractCompilerTest(enumSetOf(FeatureFlag.StateInitializerTracking)) diff --git a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/StateInitializerTransformTest.kt b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/StateInitializerTransformTest.kt index 0210e76a..41127e2f 100644 --- a/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/StateInitializerTransformTest.kt +++ b/compiler/src/test/kotlin/land/sungbin/composeinvestigator/compiler/lower/StateInitializerTransformTest.kt @@ -7,8 +7,10 @@ package land.sungbin.composeinvestigator.compiler.lower +import kotlin.test.Ignore import land.sungbin.composeinvestigator.compiler.FeatureFlag import land.sungbin.composeinvestigator.compiler._compilation.AbstractCompilerTest import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf +@Ignore("TODO") class StateInitializerTransformTest : AbstractCompilerTest(enumSetOf(FeatureFlag.StateInitializerTracking)) From 704290349c9d1c2383cd8707a5df99fe707cb2e5 Mon Sep 17 00:00:00 2001 From: Ji Sungbin Date: Wed, 25 Sep 2024 22:02:38 +0900 Subject: [PATCH 3/4] Uses JDK 22 --- build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index b372675e..90fa5165 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,6 +9,7 @@ import com.adarshr.gradle.testlogger.TestLoggerExtension import com.adarshr.gradle.testlogger.theme.ThemeType import com.diffplug.gradle.spotless.BaseKotlinExtension import com.diffplug.gradle.spotless.SpotlessExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -82,6 +83,12 @@ allprojects { ) } } + + afterEvaluate { + extensions.configure { + jvmToolchain(22) + } + } } subprojects { From 014941aae3b22f0953cb76e2730169fb11608852 Mon Sep 17 00:00:00 2001 From: Ji Sungbin Date: Wed, 25 Sep 2024 22:02:43 +0900 Subject: [PATCH 4/4] Cleanup --- .../compiler/ComposeInvestigatorPluginRegistrar.kt | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/ComposeInvestigatorPluginRegistrar.kt b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/ComposeInvestigatorPluginRegistrar.kt index 77b8c708..e8f5465b 100644 --- a/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/ComposeInvestigatorPluginRegistrar.kt +++ b/compiler/src/main/kotlin/land/sungbin/composeinvestigator/compiler/ComposeInvestigatorPluginRegistrar.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.config.messageCollector import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter public class ComposeInvestigatorPluginRegistrar : ComponentRegistrar { - override val supportsK2: Boolean = true + override val supportsK2: Boolean get() = true // This deprecated override is safe to use up to Kotlin 2.1.0 by KT-55300. // Also see: https://youtrack.jetbrains.com/issue/KT-52665/Deprecate-ComponentRegistrar#focus=Change-27-7999959.0-0 diff --git a/settings.gradle.kts b/settings.gradle.kts index 5bc9ae9a..5600a1e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -68,7 +68,7 @@ dependencyResolutionManagement { include( ":runtime", ":compiler", - ":compiler-integration-test", ":compiler-gradle-plugin", + // ":compiler-integration-test", // ":sample", )