From be04fbd5bbd54ccf03df14bf49b609661c2efedd Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Wed, 29 Apr 2020 19:14:12 +0700 Subject: [PATCH] [Commonizer] More accurate verification of annotations in tests --- .../commonizer/mergedtree/ir/CirAnnotation.kt | 40 ++-------- .../commonizer/mergedtree/ir/CirProperty.kt | 6 +- .../descriptors/commonizer/utils/constants.kt | 56 ++++++++++++++ .../utils/ComparingDeclarationsVisitor.kt | 76 +++++++++++++++---- 4 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/utils/constants.kt diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt index edb27b49e84..62c12c36dfa 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt @@ -6,11 +6,9 @@ package org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ir import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.descriptors.commonizer.utils.Interner -import org.jetbrains.kotlin.descriptors.commonizer.utils.hashCode -import org.jetbrains.kotlin.descriptors.commonizer.utils.appendHashCode +import org.jetbrains.kotlin.descriptors.commonizer.utils.* +import org.jetbrains.kotlin.descriptors.commonizer.utils.checkConstantSupportedInCommonization import org.jetbrains.kotlin.descriptors.commonizer.utils.intern -import org.jetbrains.kotlin.descriptors.commonizer.utils.isUnderStandardKotlinPackages import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.constants.* @@ -21,7 +19,11 @@ class CirAnnotation private constructor(original: AnnotationDescriptor) { init { allValueArguments.forEach { (name, constantValue) -> - checkSupportedInCommonization(constantValue) { "${original::class.java}, $original[$name]" } + checkConstantSupportedInCommonization( + constantValue = constantValue, + constantName = name, + owner = original + ) } } @@ -51,31 +53,3 @@ class CirAnnotation private constructor(original: AnnotationDescriptor) { fun create(original: AnnotationDescriptor): CirAnnotation = interner.intern(CirAnnotation(original)) } } - -internal fun checkSupportedInCommonization(constantValue: ConstantValue<*>, location: () -> String) { - @Suppress("TrailingComma") - return when (constantValue) { - is StringValue, - is IntegerValueConstant<*>, - is UnsignedValueConstant<*>, - is BooleanValue, - is NullValue, - is DoubleValue, - is FloatValue, - is EnumValue -> { - // OK - } - is AnnotationValue -> { - if (constantValue.value.fqName?.isUnderStandardKotlinPackages != true) - error("Only ${constantValue::class.java} const values from Kotlin standard packages are supported, $constantValue at ${location()}") - - Unit - } - is ArrayValue -> { - constantValue.value.forEachIndexed { index, innerConstantValue -> - checkSupportedInCommonization(innerConstantValue) { "${location()}[$index]" } - } - } - else -> error("Unsupported const value type: ${constantValue::class.java}, $constantValue at ${location()}") - } -} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirProperty.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirProperty.kt index a3ad1c95b58..bb6c80cb4bc 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirProperty.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirProperty.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ir.CirGetter.Companion.toGetter import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ir.CirSetter.Companion.toSetter import org.jetbrains.kotlin.descriptors.commonizer.utils.Interner +import org.jetbrains.kotlin.descriptors.commonizer.utils.checkConstantSupportedInCommonization import org.jetbrains.kotlin.resolve.constants.ConstantValue interface CirProperty : CirFunctionOrProperty { @@ -58,7 +59,10 @@ class CirPropertyImpl(original: PropertyDescriptor) : CirFunctionOrPropertyImpl< init { compileTimeInitializer?.let { compileTimeInitializer -> - checkSupportedInCommonization(compileTimeInitializer) { "${original::class.java}, $original" } + checkConstantSupportedInCommonization( + constantValue = compileTimeInitializer, + owner = original + ) } } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/utils/constants.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/utils/constants.kt new file mode 100644 index 00000000000..1765cb62fd4 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/utils/constants.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.descriptors.commonizer.utils + +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.constants.* + +internal fun checkConstantSupportedInCommonization( + constantValue: ConstantValue<*>, + constantName: Name? = null, + owner: Any, + onError: (String) -> Nothing = ::error +) { + checkConstantSupportedInCommonization( + constantValue = constantValue, + location = { "${owner::class.java}, $owner" + constantName?.asString()?.let { "[$it]" } }, + onError = onError + ) +} + +private fun checkConstantSupportedInCommonization( + constantValue: ConstantValue<*>, + location: () -> String, + onError: (String) -> Nothing +) { + @Suppress("TrailingComma") + when (constantValue) { + is StringValue, + is IntegerValueConstant<*>, + is UnsignedValueConstant<*>, + is BooleanValue, + is NullValue, + is DoubleValue, + is FloatValue, + is EnumValue -> { + // OK + } + is AnnotationValue -> { + if (constantValue.value.fqName?.isUnderStandardKotlinPackages != true) + onError("Only ${constantValue::class.java} const values from Kotlin standard packages are supported, $constantValue at ${location()}") + } + is ArrayValue -> { + constantValue.value.forEachIndexed { index, innerConstantValue -> + checkConstantSupportedInCommonization( + constantValue = innerConstantValue, + location = { "${location()}[$index]" }, + onError = onError + ) + } + } + else -> onError("Unsupported const value type: ${constantValue::class.java}, $constantValue at ${location()}") + } +} diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt index 50d579f6e27..563f8845bba 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt @@ -6,9 +6,12 @@ package org.jetbrains.kotlin.descriptors.commonizer.utils import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.* import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType @@ -394,14 +397,52 @@ internal class ComparingDeclarationsVisitor( visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Receiver parameter annotations")) } - private fun visitAnnotations(expected: Annotations?, actual: Annotations?, context: Context) { if (expected === actual || (expected?.isEmpty() != false && actual?.isEmpty() != false)) return - val expectedAnnotationFqNames: Set = expected?.mapTo(HashSet()) { it.fqName } ?: emptySet() - val actualAnnotationFqNames: Set = actual?.mapTo(HashSet()) { it.fqName } ?: emptySet() + fun AnnotationDescriptor.getMandatoryFqName(): FqName = fqName ?: context.fail("No FQ name for annotation $this") - context.assertSetsEqual(expectedAnnotationFqNames, actualAnnotationFqNames, "annotations") + val expectedAnnotations: Map = expected?.associateBy { it.getMandatoryFqName() }.orEmpty() + val actualAnnotations: Map = actual?.associateBy { it.getMandatoryFqName() }.orEmpty() + + context.assertSetsEqual(expectedAnnotations.keys, actualAnnotations.keys, "annotation FQ names") + + for (annotationFqName in expectedAnnotations.keys) { + val expectedAnnotation = expectedAnnotations.getValue(annotationFqName) + val actualAnnotation = actualAnnotations.getValue(annotationFqName) + + visitAnnotation(expectedAnnotation, actualAnnotation, context.nextLevel("Annotation $annotationFqName")) + } + } + + private fun visitAnnotation(expected: AnnotationDescriptor, actual: AnnotationDescriptor, context: Context) { + visitType(expected.type, actual.type, context.nextLevel("annotation type")) + + val expectedValueArguments: Map> = expected.allValueArguments + val actualValueArguments: Map> = actual.allValueArguments + + context.assertSetsEqual(expectedValueArguments.keys, actualValueArguments.keys, "annotation value argument names") + + for (name in expectedValueArguments.keys) { + val expectedValueArgument = expectedValueArguments.getValue(name) + checkConstantSupportedInCommonization( + constantValue = expectedValueArgument, + constantName = name, + owner = expected, + onError = { context.fail(it) } + ) + + val actualValueArgument = actualValueArguments.getValue(name) + checkConstantSupportedInCommonization( + constantValue = actualValueArgument, + constantName = name, + owner = actual, + onError = { context.fail(it) } + ) + + context.assertEquals(expectedValueArgument::class, actualValueArgument::class, "annotation value argument classe") + context.assertEquals(expectedValueArgument.value, actualValueArgument.value, "annotation value argument value") + } } private fun visitType(expected: KotlinType?, actual: KotlinType?, context: Context) { @@ -452,8 +493,7 @@ internal class ComparingDeclarationsVisitor( fail( buildString { append("Comparing $subject:\n") - append("$expected is not equal to $actual\n") - append(this@assertEquals.toString()) + append("\"$expected\" is not equal to \"$actual\"\n") } ) } @@ -472,17 +512,27 @@ internal class ComparingDeclarationsVisitor( if (expectedMinusActual.isNotEmpty() || actualMinusExpected.isNotEmpty()) fail( buildString { - append("Comparing $subject:\n") - append("$expected is not equal to $actual\n") - append("Expected size: ${expected.size}\n") - append("Actual size: ${actual.size}\n") - append("Expected minus actual: $expectedMinusActual\n") - append("Actual minus expected: $actualMinusExpected\n") - append(this@assertSetsEqual.toString()) + appendLine("Comparing $subject:") + appendLine("$expected is not equal to $actual") + appendLine("Expected size: ${expected.size}") + appendLine("Actual size: ${actual.size}") + appendLine("Expected minus actual: $expectedMinusActual") + appendLine("Actual minus expected: $actualMinusExpected") } ) } + private fun Context.fail(message: String): Nothing { + kotlin.test.fail( + buildString { + if (message.isNotEmpty()) { + if (message.last() != '\n') appendLine(message) else append(message) + } + append(this@fail.toString()) + } + ) + } + override fun visitPackageViewDescriptor(expected: PackageViewDescriptor, context: Context) = fail("Comparison of package views not supported")