Load WasExperimental values correctly for associated declarations

For example, if a class is `@SinceKotlin("X")
@WasExperimental(M::class)`, then its constructor should also be
accessible if API < X, provided that the opt-in to M is given
This commit is contained in:
Alexander Udalov
2018-05-04 15:12:47 +02:00
parent 9995438b37
commit 1f0d3d9c7c
4 changed files with 63 additions and 17 deletions
@@ -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<FqName>
)
/**
* @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<B<C>>') 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
}
@@ -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(): <!API_NOT_AVAILABLE!>Foo<!> = <!UNRESOLVED_REFERENCE!>Foo<!>()
@@ -17,3 +19,5 @@ fun t2() = object : <!UNRESOLVED_REFERENCE, API_NOT_AVAILABLE, API_NOT_AVAILABLE
fun t3(): Bar? = <!UNRESOLVED_REFERENCE!>Bar<!>()
fun t4(): Baz = <!UNRESOLVED_REFERENCE!>Baz<!>()
fun t5(): <!API_NOT_AVAILABLE!>Quux<!> = <!UNRESOLVED_REFERENCE!>Quux<!>()
@@ -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
}
@@ -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
}