[FIR] Check standalone companion object qualifiers are visible

This commit is contained in:
Brian Norman
2023-09-06 18:25:21 -05:00
committed by Space Team
parent 2603077db9
commit e6669b51d4
11 changed files with 97 additions and 17 deletions
@@ -833,8 +833,21 @@ fun ConeKotlinType.leastUpperBound(session: FirSession): ConeKotlinType {
fun ConeKotlinType.fullyExpandedClassId(session: FirSession): ClassId? { fun ConeKotlinType.fullyExpandedClassId(session: FirSession): ClassId? {
return fullyExpandedType(session).classId return fullyExpandedType(session).classId
} }
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
fun ConeKotlinType.hasDiagnosticKind(kind: DiagnosticKind): Boolean { fun ConeKotlinType.hasDiagnosticKind(kind: DiagnosticKind): Boolean {
contract { returns(true) implies (this@hasDiagnosticKind is ConeErrorType) } contract { returns(true) implies (this@hasDiagnosticKind is ConeErrorType) }
return this is ConeErrorType && (diagnostic as? ConeSimpleDiagnostic)?.kind == kind return this is ConeErrorType && (diagnostic as? ConeSimpleDiagnostic)?.kind == kind
} }
fun FirResolvedQualifier.isStandalone(
context: CheckerContext,
): Boolean {
val lastQualifiedAccess = context.callsOrAssignments.lastOrNull() as? FirQualifiedAccessExpression
// Note: qualifier isn't standalone when it's in receiver (SomeClass.foo) or getClass (SomeClass::class) position
if (lastQualifiedAccess?.explicitReceiver === this || lastQualifiedAccess?.dispatchReceiver === this) return false
val lastGetClass = context.getClassCalls.lastOrNull()
if (lastGetClass?.argument === this) return false
return true
}
@@ -10,9 +10,9 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.isStandalone
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
@@ -22,11 +22,7 @@ import org.jetbrains.kotlin.fir.types.resolvedType
object FirStandaloneQualifierChecker : FirResolvedQualifierChecker() { object FirStandaloneQualifierChecker : FirResolvedQualifierChecker() {
override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) {
val lastQualifiedAccess = context.callsOrAssignments.lastOrNull() as? FirQualifiedAccessExpression if (!expression.isStandalone(context)) return
// Note: qualifier isn't standalone when it's in receiver (SomeClass.foo) or getClass (SomeClass::class) position
if (lastQualifiedAccess?.explicitReceiver === expression || lastQualifiedAccess?.dispatchReceiver === expression) return
val lastGetClass = context.getClassCalls.lastOrNull()
if (lastGetClass?.argument === expression) return
// Note: if it's real Unit, it will be filtered by ClassKind.OBJECT check below in reportErrorOn // Note: if it's real Unit, it will be filtered by ClassKind.OBJECT check below in reportErrorOn
if (!expression.resolvedType.isUnit) return if (!expression.resolvedType.isUnit) return
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.isStandalone
import org.jetbrains.kotlin.fir.analysis.diagnostics.toInvisibleReferenceDiagnostic import org.jetbrains.kotlin.fir.analysis.diagnostics.toInvisibleReferenceDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
import org.jetbrains.kotlin.fir.expressions.FirErrorResolvedQualifier import org.jetbrains.kotlin.fir.expressions.FirErrorResolvedQualifier
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
@@ -18,20 +20,22 @@ import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeVisibilityError
import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.toSymbol import org.jetbrains.kotlin.fir.types.toSymbol
import org.jetbrains.kotlin.fir.visibilityChecker import org.jetbrains.kotlin.fir.visibilityChecker
object FirVisibilityQualifierChecker : FirResolvedQualifierChecker() { object FirVisibilityQualifierChecker : FirResolvedQualifierChecker() {
override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) {
checkClassLikeSymbol(expression.symbol ?: return, expression, context, reporter) checkClassLikeSymbol(expression.symbol ?: return, expression, expression.isStandalone(context), context, reporter)
} }
@OptIn(SymbolInternals::class) @OptIn(SymbolInternals::class)
private fun checkClassLikeSymbol( private fun checkClassLikeSymbol(
symbol: FirClassLikeSymbol<*>, symbol: FirClassLikeSymbol<*>,
expression: FirResolvedQualifier, expression: FirResolvedQualifier,
isStandalone: Boolean,
context: CheckerContext, context: CheckerContext,
reporter: DiagnosticReporter reporter: DiagnosticReporter,
) { ) {
val firFile = context.containingFile ?: return val firFile = context.containingFile ?: return
val firClassLikeDeclaration = symbol.fir val firClassLikeDeclaration = symbol.fir
@@ -50,14 +54,35 @@ object FirVisibilityQualifierChecker : FirResolvedQualifierChecker() {
return return
} }
// Validate standalone references to companion objects are visible. Qualified use is validated
// by call resolution cone diagnostics in coneDiagnosticToFirDiagnostic.
if (isStandalone) {
val invisibleCompanion = expression.symbol?.fullyExpandedClass(context.session)?.toInvisibleCompanion(context)
if (invisibleCompanion != null) {
if (expression !is FirErrorResolvedQualifier || expression.diagnostic !is ConeVisibilityError) {
reporter.report(invisibleCompanion.toInvisibleReferenceDiagnostic(expression.source), context)
}
return
}
}
if (firClassLikeDeclaration is FirTypeAlias) { if (firClassLikeDeclaration is FirTypeAlias) {
firClassLikeDeclaration.expandedConeType?.toSymbol(context.session)?.let { firClassLikeDeclaration.expandedConeType?.toSymbol(context.session)?.let {
checkClassLikeSymbol(it, expression, context, reporter) checkClassLikeSymbol(it, expression, isStandalone, context, reporter)
} }
} }
symbol.getOwnerLookupTag()?.toSymbol(context.session)?.let { symbol.getOwnerLookupTag()?.toSymbol(context.session)?.let {
checkClassLikeSymbol(it, expression, context, reporter) checkClassLikeSymbol(it, expression, isStandalone = false, context, reporter)
}
}
@OptIn(SymbolInternals::class)
private fun FirRegularClassSymbol.toInvisibleCompanion(context: CheckerContext): FirRegularClassSymbol? {
val firFile = context.containingFile ?: return null
return companionObjectSymbol?.takeIf {
!context.session.visibilityChecker.isClassLikeVisible(it.fir, context.session, firFile, context.containingDeclarations)
} }
} }
} }
@@ -3,9 +3,10 @@ package foo
fun test() { fun test() {
A.d A.d
A.Companion.<!INVISIBLE_REFERENCE!>f<!> A.Companion.<!INVISIBLE_REFERENCE!>f<!>
B.D B.<!INVISIBLE_REFERENCE!>D<!>
CCC <!INVISIBLE_REFERENCE!>CCC<!>
CCC.<!INVISIBLE_REFERENCE!>classObjectVar<!> CCC.<!INVISIBLE_REFERENCE!>classObjectVar<!>
E.F.G
} }
class A() { class A() {
@@ -28,3 +29,10 @@ class CCC() {
val classObjectVar = 3 val classObjectVar = 3
} }
} }
class E {
class F {
companion object G
}
private companion object
}
@@ -6,6 +6,7 @@ fun test() {
B.<!INVISIBLE_MEMBER!>D<!> B.<!INVISIBLE_MEMBER!>D<!>
<!INVISIBLE_MEMBER!>CCC<!> <!INVISIBLE_MEMBER!>CCC<!>
CCC.<!INVISIBLE_MEMBER!>classObjectVar<!> CCC.<!INVISIBLE_MEMBER!>classObjectVar<!>
E.F.G
} }
class A() { class A() {
@@ -28,3 +29,10 @@ class CCC() {
val classObjectVar = 3 val classObjectVar = 3
} }
} }
class E {
class F {
companion object G
}
private companion object
}
@@ -60,4 +60,32 @@ package foo {
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
} }
} }
public final class E {
public constructor E()
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
private companion object Companion {
private constructor Companion()
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
}
public final class F {
public constructor F()
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
public companion object G {
private constructor G()
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
}
}
}
} }
@@ -12,6 +12,8 @@
/b.kt:(225,226): error: Cannot access 'class B : Any': it is private in file. /b.kt:(225,226): error: Cannot access 'class B : Any': it is private in file.
/b.kt:(234,235): error: Cannot access 'companion object Companion : Any': it is private in 'a/C'.
/b.kt:(243,244): error: Cannot access 'class D : Any': it is private in file. /b.kt:(243,244): error: Cannot access 'class D : Any': it is private in file.
/b.kt:(263,264): error: Cannot access 'class B : Any': it is private in file. /b.kt:(263,264): error: Cannot access 'class B : Any': it is private in file.
@@ -41,7 +41,7 @@ import a.<!INVISIBLE_REFERENCE!>D<!>.<!INVISIBLE_REFERENCE!>Companion<!>.quux
fun test() { fun test() {
f(A) f(A)
f(<!INVISIBLE_REFERENCE!>B<!>) f(<!INVISIBLE_REFERENCE!>B<!>)
f(C) f(<!INVISIBLE_REFERENCE!>C<!>)
f(<!INVISIBLE_REFERENCE!>D<!>) f(<!INVISIBLE_REFERENCE!>D<!>)
A.foo() A.foo()
@@ -7,4 +7,4 @@ enum class E {
fun foo() = E.values() fun foo() = E.values()
fun bar() = E.valueOf("ENTRY") fun bar() = E.valueOf("ENTRY")
fun baz() = E.ENTRY fun baz() = E.ENTRY
fun <!EXPOSED_FUNCTION_RETURN_TYPE!>quux<!>() = E fun <!EXPOSED_FUNCTION_RETURN_TYPE!>quux<!>() = <!INVISIBLE_REFERENCE!>E<!>
@@ -17,7 +17,7 @@ class E private constructor() {
} }
val a = <!NO_COMPANION_OBJECT!>A<!> val a = <!NO_COMPANION_OBJECT!>A<!>
val <!EXPOSED_PROPERTY_TYPE!>b<!> = B val <!EXPOSED_PROPERTY_TYPE!>b<!> = <!INVISIBLE_REFERENCE!>B<!>
val c = <!NO_COMPANION_OBJECT!>C<!> val c = <!NO_COMPANION_OBJECT!>C<!>
val d = D val d = D
val e = E(42) val e = E(42)
@@ -4,5 +4,5 @@ class C {
typealias CAlias = C typealias CAlias = C
val <!EXPOSED_PROPERTY_TYPE!>test1<!> = CAlias val <!EXPOSED_PROPERTY_TYPE!>test1<!> = <!INVISIBLE_REFERENCE!>CAlias<!>
val <!EXPOSED_PROPERTY_TYPE!>test1a<!> = C val <!EXPOSED_PROPERTY_TYPE!>test1a<!> = <!INVISIBLE_REFERENCE!>C<!>