[FIR] Move check for _private-to-this_ visibility into checker

^KT-55446
^KT-65790 Fixed
This commit is contained in:
Dmitriy Novozhilov
2024-02-14 12:02:53 +02:00
committed by Space Team
parent 5fe1e0c1a5
commit c64575f4a2
18 changed files with 239 additions and 131 deletions
@@ -54,6 +54,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirMissingDependencyClassChecker,
FirMissingDependencySupertypeInQualifiedAccessExpressionsChecker,
FirArrayOfNothingQualifierChecker,
FirPrivateToThisAccessChecker,
)
override val callCheckers: Set<FirCallChecker>
@@ -0,0 +1,158 @@
/*
* Copyright 2010-2024 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.fir.analysis.checkers.expression
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.diagnostics.*
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.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.toReference
import org.jetbrains.kotlin.fir.references.FirResolvedErrorReference
import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.references.resolved
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeSetterVisibilityError
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeVisibilityError
import org.jetbrains.kotlin.fir.resolve.toClassSymbol
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.unwrapFakeOverrides
import org.jetbrains.kotlin.types.EnrichedProjectionKind
import org.jetbrains.kotlin.types.Variance
object FirPrivateToThisAccessChecker : FirQualifiedAccessExpressionChecker(MppCheckerKind.Common) {
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val reference = expression.calleeReference.resolved ?: return
if (reference is FirResolvedErrorReference) {
// If there was a visibility diagnostic, no need to report another one about visibility
when (reference.diagnostic) {
is ConeVisibilityError,
is ConeSetterVisibilityError -> return
}
}
val dispatchReceiver = expression.dispatchReceiver ?: return
val symbol = reference.toResolvedCallableSymbol(discardErrorReference = true) ?: return
if (symbol.visibility != Visibilities.Private) return
val session = context.session
val containingClassSymbol = symbol.containingClassLookupTag()?.toClassSymbol(session)
if (!isPrivateToThis(symbol.unwrapFakeOverrides(), containingClassSymbol, session)) return
val invisible = when (val receiverReference = dispatchReceiver.toReference(session)) {
is FirThisReference -> receiverReference.boundSymbol != containingClassSymbol
else -> true
}
if (invisible) {
reporter.reportOn(
source = expression.source,
factory = FirErrors.INVISIBLE_REFERENCE,
symbol,
Visibilities.PrivateToThis,
symbol.callableId.classId,
context,
)
}
}
private fun isPrivateToThis(
symbol: FirCallableSymbol<*>,
containingClassSymbol: FirClassSymbol<*>?,
session: FirSession
): Boolean {
if (containingClassSymbol == null) return false
if (symbol is FirConstructorSymbol) return false
if (containingClassSymbol.typeParameterSymbols.all { it.variance == Variance.INVARIANT }) return false
if (symbol.receiverParameter?.typeRef?.coneType?.contradictsWith(Variance.IN_VARIANCE, session) == true) {
return true
}
if (symbol.resolvedReturnType.contradictsWith(
if (symbol is FirPropertySymbol && symbol.isVar) Variance.INVARIANT
else Variance.OUT_VARIANCE,
session
)
) {
return true
}
if (symbol is FirFunctionSymbol<*>) {
for (parameter in symbol.valueParameterSymbols) {
if (parameter.resolvedReturnType.contradictsWith(Variance.IN_VARIANCE, session)) {
return true
}
}
}
return false
}
private fun ConeKotlinType.contradictsWith(requiredVariance: Variance, session: FirSession): Boolean {
when (this) {
is ConeLookupTagBasedType -> {
if (this is ConeTypeParameterType) {
return !this.lookupTag.typeParameterSymbol.variance.allowsPosition(requiredVariance)
}
if (this is ConeClassLikeType) {
// It's safe to access fir here, because later we access only variance of type parameters of the class
// And variance can not be changed after raw fir stage
@OptIn(SymbolInternals::class)
val classLike = this.lookupTag.toSymbol(session)?.fir ?: return false
for ((index, argument) in this.typeArguments.withIndex()) {
val typeParameterRef = classLike.typeParameters.getOrNull(index)
if (typeParameterRef !is FirTypeParameter) continue
val requiredVarianceForArgument = when (
EnrichedProjectionKind.getEffectiveProjectionKind(typeParameterRef.variance, argument.variance)
) {
EnrichedProjectionKind.OUT -> requiredVariance
EnrichedProjectionKind.IN -> requiredVariance.opposite()
EnrichedProjectionKind.INV -> Variance.INVARIANT
EnrichedProjectionKind.STAR -> continue // CONFLICTING_PROJECTION error was reported
}
val argType = argument.type ?: continue
if (argType.contradictsWith(requiredVarianceForArgument, session)) {
return true
}
}
}
}
is ConeFlexibleType -> {
return lowerBound.contradictsWith(requiredVariance, session)
}
is ConeDefinitelyNotNullType -> {
return original.contradictsWith(requiredVariance, session)
}
is ConeIntersectionType -> {
return this.intersectedTypes.any { it.contradictsWith(requiredVariance, session) }
}
is ConeCapturedType -> {
// Looks like not possible here
return false
}
is ConeIntegerConstantOperatorType,
is ConeIntegerLiteralConstantType,
is ConeStubTypeForChainInference,
is ConeStubTypeForTypeVariableInSubtyping,
is ConeTypeVariableType,
-> return false
}
return false
}
private val ConeTypeProjection.variance: Variance
get() = when (this.kind) {
ProjectionKind.STAR -> Variance.OUT_VARIANCE
ProjectionKind.IN -> Variance.IN_VARIANCE
ProjectionKind.OUT -> Variance.OUT_VARIANCE
ProjectionKind.INVARIANT -> Variance.INVARIANT
}
}
@@ -61,6 +61,12 @@ fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSym
}
}
/**
* @see toSymbol
*/
fun ConeClassLikeLookupTag.toClassSymbol(session: FirSession): FirClassSymbol<*>? =
toSymbol(session) as? FirClassSymbol<*>
/**
* @see toSymbol
*/
@@ -15,15 +15,12 @@ import org.jetbrains.kotlin.fir.extensions.FirStatusTransformerExtension
import org.jetbrains.kotlin.fir.extensions.extensionService
import org.jetbrains.kotlin.fir.extensions.statusTransformerExtensions
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
import org.jetbrains.kotlin.fir.toEffectiveVisibility
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.fir.visibilityChecker
import org.jetbrains.kotlin.types.EnrichedProjectionKind
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
class FirStatusResolver(
@@ -41,7 +38,7 @@ class FirStatusResolver(
)
private val MODIFIERS_FROM_OVERRIDDEN: List<FirDeclarationStatusImpl.Modifier> =
FirDeclarationStatusImpl.Modifier.values().toList() - NOT_INHERITED_MODIFIERS
FirDeclarationStatusImpl.Modifier.entries - NOT_INHERITED_MODIFIERS
}
private val extensionStatusTransformers = session.extensionService.statusTransformerExtensions
@@ -247,18 +244,7 @@ class FirStatusResolver(
isLocal -> Visibilities.Local
else -> resolveVisibility(declaration, containingClass, containingProperty, overriddenStatuses)
}
Visibilities.Private -> when {
declaration is FirPropertyAccessor -> if (containingProperty?.visibility == Visibilities.PrivateToThis) {
Visibilities.PrivateToThis
} else {
Visibilities.Private
}
isPrivateToThis(declaration, containingClass) -> Visibilities.PrivateToThis
else -> Visibilities.Private
}
Visibilities.Private -> Visibilities.Private
else -> status.visibility
}
@@ -317,97 +303,6 @@ class FirStatusResolver(
return status.resolved(visibility, modality, effectiveVisibility)
}
private fun isPrivateToThis(
declaration: FirDeclaration,
containingClass: FirClass?,
): Boolean {
if (containingClass == null) return false
if (declaration !is FirCallableDeclaration) return false
if (declaration is FirConstructor) return false
if (containingClass.typeParameters.all { it.symbol.variance == Variance.INVARIANT }) return false
if (declaration.receiverParameter?.typeRef?.contradictsWith(Variance.IN_VARIANCE) == true) {
return true
}
if (declaration.returnTypeRef.contradictsWith(
if (declaration is FirProperty && declaration.isVar) Variance.INVARIANT
else Variance.OUT_VARIANCE
)
) {
return true
}
if (declaration is FirFunction) {
for (parameter in declaration.valueParameters) {
if (parameter.returnTypeRef.contradictsWith(Variance.IN_VARIANCE)) {
return true
}
}
}
return false
}
private fun FirTypeRef.contradictsWith(requiredVariance: Variance): Boolean {
val type = coneTypeSafe<ConeKotlinType>() ?: return false
return contradictsWith(type, requiredVariance)
}
private fun contradictsWith(type: ConeKotlinType, requiredVariance: Variance): Boolean {
when (type) {
is ConeLookupTagBasedType -> {
if (type is ConeTypeParameterType) {
return !type.lookupTag.typeParameterSymbol.fir.variance.allowsPosition(requiredVariance)
}
if (type is ConeClassLikeType) {
val classLike = type.lookupTag.toSymbol(session)?.fir
for ((index, argument) in type.typeArguments.withIndex()) {
val typeParameterRef = classLike?.typeParameters?.getOrNull(index)
if (typeParameterRef !is FirTypeParameter) continue
val requiredVarianceForArgument = when (
EnrichedProjectionKind.getEffectiveProjectionKind(typeParameterRef.variance, argument.variance)
) {
EnrichedProjectionKind.OUT -> requiredVariance
EnrichedProjectionKind.IN -> requiredVariance.opposite()
EnrichedProjectionKind.INV -> Variance.INVARIANT
EnrichedProjectionKind.STAR -> continue // CONFLICTING_PROJECTION error was reported
}
val argType = argument.type ?: continue
if (contradictsWith(argType, requiredVarianceForArgument)) {
return true
}
}
}
}
is ConeFlexibleType -> {
return contradictsWith(type.lowerBound, requiredVariance)
}
is ConeDefinitelyNotNullType -> {
return contradictsWith(type.original, requiredVariance)
}
is ConeIntersectionType -> {
return type.intersectedTypes.any { contradictsWith(it, requiredVariance) }
}
is ConeCapturedType -> {
// Looks like not possible here
return false
}
is ConeIntegerConstantOperatorType,
is ConeIntegerLiteralConstantType,
is ConeStubTypeForChainInference,
is ConeStubTypeForTypeVariableInSubtyping,
is ConeTypeVariableType,
-> return false
}
return false
}
private val ConeTypeProjection.variance: Variance
get() = when (this.kind) {
ProjectionKind.STAR -> Variance.OUT_VARIANCE
ProjectionKind.IN -> Variance.IN_VARIANCE
ProjectionKind.OUT -> Variance.OUT_VARIANCE
ProjectionKind.INVARIANT -> Variance.INVARIANT
}
private fun resolveVisibility(
declaration: FirDeclaration,
containingClass: FirClass?,
@@ -14,7 +14,7 @@ class Test<in I> {
apply(foo())
apply(this.foo())
with(Test<I>()) {
apply(foo()) // resolved to this@Test.foo
apply(<!INVISIBLE_REFERENCE!>foo<!>()) // K1: this@Test.foo, K2: this@with.foo, see KT-55446
apply(this.<!INVISIBLE_REFERENCE!>foo<!>())
apply(this@with.<!INVISIBLE_REFERENCE!>foo<!>())
apply(this@Test.foo())
@@ -14,7 +14,7 @@ class Test<in I> {
apply(foo())
apply(this.foo())
with(Test<I>()) {
apply(foo()) // resolved to this@Test.foo
apply(foo()) // K1: this@Test.foo, K2: this@with.foo, see KT-55446
apply(this.<!INVISIBLE_MEMBER("foo; private/*private to this*/; 'Test'")!>foo<!>())
apply(this@with.<!INVISIBLE_MEMBER("foo; private/*private to this*/; 'Test'")!>foo<!>())
apply(this@Test.foo())
@@ -14,7 +14,7 @@ class Test<in I, out O> {
apply(i)
apply(this.i)
with(Test<I, O>()) {
apply(i) // resolved to this@Test.i
apply(<!INVISIBLE_REFERENCE!>i<!>) // K1: this@Test.i, K2: this@with.i, see KT-55446
apply(this.<!INVISIBLE_REFERENCE!>i<!>)
apply(this@with.<!INVISIBLE_REFERENCE!>i<!>)
apply(this@Test.i)
@@ -14,7 +14,7 @@ class Test<in I, out O> {
apply(i)
apply(this.i)
with(Test<I, O>()) {
apply(i) // resolved to this@Test.i
apply(i) // K1: this@Test.i, K2: this@with.i, see KT-55446
apply(this.<!INVISIBLE_MEMBER("i; private/*private to this*/; 'Test'")!>i<!>)
apply(this@with.<!INVISIBLE_MEMBER("i; private/*private to this*/; 'Test'")!>i<!>)
apply(this@Test.i)
@@ -14,7 +14,7 @@ class Test<in I, out O> {
i = getT()
this.i = getT()
with(Test<I, O>()) {
i = getT() // resolved to this@Test.i
<!INVISIBLE_REFERENCE!>i<!> = getT() // K1: this@Test.i, K2: this@with.i, see KT-55446
this.<!INVISIBLE_REFERENCE!>i<!> = getT()
this@with.<!INVISIBLE_REFERENCE!>i<!> = getT()
this@Test.i = getT()
@@ -14,7 +14,7 @@ class Test<in I, out O> {
i = getT()
this.i = getT()
with(Test<I, O>()) {
i = getT() // resolved to this@Test.i
i = getT() // K1: this@Test.i, K2: this@with.i, see KT-55446
this.<!INVISIBLE_MEMBER("i; private/*private to this*/; 'Test'")!>i<!> = getT()
this@with.<!INVISIBLE_MEMBER("i; private/*private to this*/; 'Test'")!>i<!> = getT()
this@Test.i = getT()
@@ -1,5 +1,6 @@
// FIR_DUMP
// explicit types
class A<in T>(t: T) {
private val t: T = t // PRIVATE_TO_THIS
@@ -24,3 +25,13 @@ class A<in T>(t: T) {
}
}
}
// implicit types
class C<in T>(t: T) {
private val t: T = t
private val tt = t
fun foo(a: C<String>) {
val x: String = a.<!INVISIBLE_REFERENCE!>tt<!>
}
}
@@ -4,8 +4,8 @@ FILE: privateToThis.fir.kt
super<R|kotlin/Any|>()
}
private/*private to this*/ final val t: R|T| = R|<local>/t|
private/*private to this*/ get(): R|T|
private final val t: R|T| = R|<local>/t|
private get(): R|T|
private final val i: R|A.B<T>| = this@R|/A|.R|SubstitutionOverride</A.B.B>|()
private get(): R|A.B<T>|
@@ -16,11 +16,11 @@ FILE: privateToThis.fir.kt
}
public final fun foo(a: R|A<kotlin/String>|): R|kotlin/Unit| {
lval x: R|kotlin/String| = R|<local>/a|.R|SubstitutionOverride</A.t: R|kotlin/String|><HIDDEN: /A.t is invisible>#|
lval x: R|kotlin/String| = R|<local>/a|.R|SubstitutionOverride</A.t: R|kotlin/String|>|
}
public final fun bar(a: R|A<*>|): R|kotlin/Unit| {
R|<local>/a|.R|SubstitutionOverride</A.t: R|CapturedType(*)|><HIDDEN: /A.t is invisible>#|
R|<local>/a|.R|SubstitutionOverride</A.t: R|CapturedType(*)|>|
}
public final inner class B<in Outer(T)> : R|kotlin/Any| {
@@ -35,3 +35,19 @@ FILE: privateToThis.fir.kt
}
}
public final class C<in T> : R|kotlin/Any| {
public constructor<in T>(t: R|T|): R|C<T>| {
super<R|kotlin/Any|>()
}
private final val t: R|T| = R|<local>/t|
private get(): R|T|
private final val tt: R|T| = R|<local>/t|
private get(): R|T|
public final fun foo(a: R|C<kotlin/String>|): R|kotlin/Unit| {
lval x: R|kotlin/String| = R|<local>/a|.R|SubstitutionOverride</C.tt: R|kotlin/String|>|
}
}
@@ -1,5 +1,6 @@
// FIR_DUMP
// explicit types
class A<in T>(t: T) {
private val t: T = t // PRIVATE_TO_THIS
@@ -24,3 +25,13 @@ class A<in T>(t: T) {
}
}
}
// implicit types
class C<in T>(t: T) {
private val t: T = t
private val tt = t
fun foo(a: C<String>) {
val x: String = a.<!INVISIBLE_MEMBER!>tt<!>
}
}
@@ -19,3 +19,14 @@ public final class A</*0*/ in T> {
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public final class C</*0*/ in T> {
public constructor C</*0*/ in T>(/*0*/ t: T)
private/*private to this*/ final val t: T
private/*private to this*/ final val tt: T
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(/*0*/ a: C<kotlin.String>): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -20,7 +20,6 @@ class Foo<in T> : Base<<!TYPE_VARIANCE_CONFLICT_ERROR!>T<!>>() {
fun bar(f: Foo<Bar>) {
val dnn = f.<!INVISIBLE_REFERENCE!>dnn<!>
// This case (and any other with non-denotable type) requires KT-55446 to be fixed
val flex = f.flex
val flex = f.<!INVISIBLE_REFERENCE!>flex<!>
}
}
@@ -20,7 +20,6 @@ class Foo<in T> : Base<<!TYPE_VARIANCE_CONFLICT_ERROR!>T<!>>() {
fun bar(f: Foo<Bar>) {
val dnn = f.<!INVISIBLE_MEMBER!>dnn<!>
// This case (and any other with non-denotable type) requires KT-55446 to be fixed
val flex = f.<!INVISIBLE_MEMBER!>flex<!>
}
}
@@ -1,19 +1,19 @@
public final class A<in I> : R|kotlin/Any| {
private/*private to this*/ final fun bas(): R|I|
private final fun bas(): R|I|
private/*private to this*/ final val foo: R|I|
private/*private to this*/ get(): R|I|
private final val foo: R|I|
private get(): R|I|
private/*private to this*/ final var bar: R|I|
private/*private to this*/ get(): R|I|
private/*private to this*/ set(value: R|I|): R|kotlin/Unit|
private final var bar: R|I|
private get(): R|I|
private set(value: R|I|): R|kotlin/Unit|
private/*private to this*/ final val val_with_accessors: R|I|
private/*private to this*/ get(): R|I|
private final val val_with_accessors: R|I|
private get(): R|I|
private/*private to this*/ final var var_with_accessors: R|I|
private/*private to this*/ get(): R|I|
private/*private to this*/ set(value: R|I|): R|kotlin/Unit|
private final var var_with_accessors: R|I|
private get(): R|I|
private set(value: R|I|): R|kotlin/Unit|
public constructor<in I>(): R|test/A<I>|
@@ -10,6 +10,7 @@ object Visibilities {
override fun mustCheckInImports(): Boolean = true
}
// K2 doesn't use this visibility, see KT-55446 for details
object PrivateToThis : Visibility("private_to_this", isPublicAPI = false) {
override val internalDisplayName: String
get() = "private/*private to this*/"