diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt index e8da09e259b..6dfc6075f72 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt @@ -27,9 +27,10 @@ sealed class SinceKotlinAccessibility { } fun DeclarationDescriptor.checkSinceKotlinVersionAccessibility(languageVersionSettings: LanguageVersionSettings): SinceKotlinAccessibility { - val version = + val value = if (this is CallableMemberDescriptor && !kind.isReal) getSinceKotlinVersionByOverridden(this) else getOwnSinceKotlinVersion() + val version = value?.apiVersion // Allow access in the following cases: // 1) There's no @SinceKotlin annotation for this descriptor @@ -37,7 +38,7 @@ fun DeclarationDescriptor.checkSinceKotlinVersionAccessibility(languageVersionSe // 3) The value as a version is not greater than our API version if (version == null || version <= languageVersionSettings.apiVersion) return SinceKotlinAccessibility.Accessible - val wasExperimentalFqNames = with(ExperimentalUsageChecker) { loadWasExperimentalMarkerFqNames() } + val wasExperimentalFqNames = value.wasExperimentalMarkerFqNames if (wasExperimentalFqNames.isNotEmpty()) { return SinceKotlinAccessibility.NotAccessibleButWasExperimental(version, wasExperimentalFqNames) } @@ -45,34 +46,59 @@ fun DeclarationDescriptor.checkSinceKotlinVersionAccessibility(languageVersionSe return SinceKotlinAccessibility.NotAccessible(version) } +private data class SinceKotlinValue( + val apiVersion: ApiVersion, + val wasExperimentalMarkerFqNames: List +) + /** * @return null if there are no overridden members or if there's at least one declaration in the hierarchy not annotated with [SinceKotlin], * or the minimal value of the version from all declarations annotated with [SinceKotlin] otherwise. */ -private fun getSinceKotlinVersionByOverridden(descriptor: CallableMemberDescriptor): ApiVersion? { - return DescriptorUtils.getAllOverriddenDeclarations(descriptor).map { it.getOwnSinceKotlinVersion() ?: return null }.min() +private fun getSinceKotlinVersionByOverridden(descriptor: CallableMemberDescriptor): SinceKotlinValue? { + // TODO: combine wasExperimentalMarkerFqNames in case of several members with the same minimal API version + return DescriptorUtils.getAllOverriddenDeclarations(descriptor).map { it.getOwnSinceKotlinVersion() ?: return null } + .minBy { it.apiVersion } } -private fun DeclarationDescriptor.getOwnSinceKotlinVersion(): ApiVersion? { +/** + * @return the maximal value of API version required by the declaration or any of its "associated" declarations (class for constructor, + * property for accessor, underlying class for type alias) along with experimental marker FQ names mentioned in the @WasExperimental + */ +private fun DeclarationDescriptor.getOwnSinceKotlinVersion(): SinceKotlinValue? { + var result: SinceKotlinValue? = null + // TODO: use-site targeted annotations - fun DeclarationDescriptor.loadAnnotationValue(): ApiVersion? = - (annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME)?.allValueArguments?.values?.singleOrNull()?.value as? String) + fun DeclarationDescriptor.consider() { + val apiVersion = (annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME)?.allValueArguments?.values?.singleOrNull()?.value as? String) ?.let(ApiVersion.Companion::parse) + if (apiVersion != null) { + // TODO: combine wasExperimentalMarkerFqNames in case of several associated declarations with the same maximal API version + if (result == null || apiVersion > result!!.apiVersion) { + result = SinceKotlinValue( + apiVersion, + with(ExperimentalUsageChecker) { loadWasExperimentalMarkerFqNames() } + ) + } + } + } - val ownVersion = loadAnnotationValue() - val ctorClass = (this as? ConstructorDescriptor)?.containingDeclaration?.loadAnnotationValue() - val property = (this as? PropertyAccessorDescriptor)?.correspondingProperty?.loadAnnotationValue() + this.consider() - val typeAliasDescriptor = (this as? TypeAliasDescriptor) ?: (this as? TypeAliasConstructorDescriptor)?.typeAliasDescriptor - ?: (this as? FakeCallableDescriptorForTypeAliasObject)?.typeAliasDescriptor + (this as? ConstructorDescriptor)?.containingDeclaration?.consider() + (this as? PropertyAccessorDescriptor)?.correspondingProperty?.consider() - val typeAlias = typeAliasDescriptor?.loadAnnotationValue() + val typeAlias = this as? TypeAliasDescriptor + ?: (this as? TypeAliasConstructorDescriptor)?.typeAliasDescriptor + ?: (this as? FakeCallableDescriptorForTypeAliasObject)?.typeAliasDescriptor + + typeAlias?.consider() // We should check only the upper-most classifier ('A' in 'A>') to guarantee binary compatibility. - val underlyingClass = typeAliasDescriptor?.classDescriptor?.loadAnnotationValue() + typeAlias?.classDescriptor?.consider() - val underlyingConstructor = (this as? TypeAliasConstructorDescriptor)?.underlyingConstructorDescriptor?.loadAnnotationValue() - val underlyingObject = (this as? FakeCallableDescriptorForTypeAliasObject)?.getReferencedObject()?.loadAnnotationValue() + (this as? TypeAliasConstructorDescriptor)?.underlyingConstructorDescriptor?.consider() + (this as? FakeCallableDescriptorForTypeAliasObject)?.getReferencedObject()?.consider() - return listOfNotNull(ownVersion, ctorClass, property, typeAlias, underlyingClass, underlyingConstructor, underlyingObject).max() + return result } diff --git a/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.kt b/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.kt index aa0e96dc4f5..a07ffd00087 100644 --- a/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.kt +++ b/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.kt @@ -8,6 +8,8 @@ class Bar @SinceKotlin("1.1") constructor() @SinceKotlin("1.0") class Baz @SinceKotlin("1.1") constructor() +@SinceKotlin("1.1") +class Quux @SinceKotlin("1.0") constructor() fun t1(): Foo = Foo() @@ -17,3 +19,5 @@ fun t2() = object : Bar() fun t4(): Baz = Baz() + +fun t5(): Quux = Quux() diff --git a/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.txt b/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.txt index 9c89074b1a2..7b8aa15040b 100644 --- a/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.txt +++ b/compiler/testData/diagnostics/tests/sourceCompatibility/apiVersion/classesAndConstructors.txt @@ -4,6 +4,7 @@ public fun t1(): Foo public fun t2(): Foo public fun t3(): Bar? public fun t4(): Baz +public fun t5(): Quux public final class Bar { @kotlin.SinceKotlin(version = "1.1") public constructor Bar() @@ -25,3 +26,10 @@ public final class Bar { public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } + +@kotlin.SinceKotlin(version = "1.1") public final class Quux { + @kotlin.SinceKotlin(version = "1.0") public constructor Quux() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt b/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt index 1b19b2717ff..57823a54dff 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt @@ -1,5 +1,6 @@ package +@kotlin.SinceKotlin(version = "1.3") @kotlin.WasExperimental(markerClass = {Marker::class}) public val newValExperimentalInThePast: kotlin.String = "" @kotlin.SinceKotlin(version = "1.3") @kotlin.WasExperimental(markerClass = {Marker::class}) public fun newFunExperimentalInThePast(): kotlin.Unit @kotlin.SinceKotlin(version = "1.3") public fun newPublishedFun(): kotlin.Unit public fun use1(): kotlin.Unit @@ -12,3 +13,10 @@ public fun use1(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } + +@kotlin.SinceKotlin(version = "1.3") @kotlin.WasExperimental(markerClass = {Marker::class}) public final class NewClassExperimentalInThePast { + public constructor NewClassExperimentalInThePast() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +}