[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? {
return fullyExpandedType(session).classId
}
@OptIn(ExperimentalContracts::class)
fun ConeKotlinType.hasDiagnosticKind(kind: DiagnosticKind): Boolean {
contract { returns(true) implies (this@hasDiagnosticKind is ConeErrorType) }
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.reportOn
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.declarations.fullyExpandedClass
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
@@ -22,11 +22,7 @@ import org.jetbrains.kotlin.fir.types.resolvedType
object FirStandaloneQualifierChecker : FirResolvedQualifierChecker() {
override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) {
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 === expression || lastQualifiedAccess?.dispatchReceiver === expression) return
val lastGetClass = context.getClassCalls.lastOrNull()
if (lastGetClass?.argument === expression) return
if (!expression.isStandalone(context)) return
// Note: if it's real Unit, it will be filtered by ClassKind.OBJECT check below in reportErrorOn
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.diagnostics.DiagnosticReporter
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.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
import org.jetbrains.kotlin.fir.expressions.FirErrorResolvedQualifier
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.symbols.SymbolInternals
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.visibilityChecker
object FirVisibilityQualifierChecker : FirResolvedQualifierChecker() {
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)
private fun checkClassLikeSymbol(
symbol: FirClassLikeSymbol<*>,
expression: FirResolvedQualifier,
isStandalone: Boolean,
context: CheckerContext,
reporter: DiagnosticReporter
reporter: DiagnosticReporter,
) {
val firFile = context.containingFile ?: return
val firClassLikeDeclaration = symbol.fir
@@ -50,14 +54,35 @@ object FirVisibilityQualifierChecker : FirResolvedQualifierChecker() {
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) {
firClassLikeDeclaration.expandedConeType?.toSymbol(context.session)?.let {
checkClassLikeSymbol(it, expression, context, reporter)
checkClassLikeSymbol(it, expression, isStandalone, context, reporter)
}
}
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() {
A.d
A.Companion.<!INVISIBLE_REFERENCE!>f<!>
B.D
CCC
B.<!INVISIBLE_REFERENCE!>D<!>
<!INVISIBLE_REFERENCE!>CCC<!>
CCC.<!INVISIBLE_REFERENCE!>classObjectVar<!>
E.F.G
}
class A() {
@@ -28,3 +29,10 @@ class CCC() {
val classObjectVar = 3
}
}
class E {
class F {
companion object G
}
private companion object
}
@@ -6,6 +6,7 @@ fun test() {
B.<!INVISIBLE_MEMBER!>D<!>
<!INVISIBLE_MEMBER!>CCC<!>
CCC.<!INVISIBLE_MEMBER!>classObjectVar<!>
E.F.G
}
class A() {
@@ -28,3 +29,10 @@ class CCC() {
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 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:(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:(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() {
f(A)
f(<!INVISIBLE_REFERENCE!>B<!>)
f(C)
f(<!INVISIBLE_REFERENCE!>C<!>)
f(<!INVISIBLE_REFERENCE!>D<!>)
A.foo()
@@ -7,4 +7,4 @@ enum class E {
fun foo() = E.values()
fun bar() = E.valueOf("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 <!EXPOSED_PROPERTY_TYPE!>b<!> = B
val <!EXPOSED_PROPERTY_TYPE!>b<!> = <!INVISIBLE_REFERENCE!>B<!>
val c = <!NO_COMPANION_OBJECT!>C<!>
val d = D
val e = E(42)
@@ -4,5 +4,5 @@ class C {
typealias CAlias = C
val <!EXPOSED_PROPERTY_TYPE!>test1<!> = CAlias
val <!EXPOSED_PROPERTY_TYPE!>test1a<!> = C
val <!EXPOSED_PROPERTY_TYPE!>test1<!> = <!INVISIBLE_REFERENCE!>CAlias<!>
val <!EXPOSED_PROPERTY_TYPE!>test1a<!> = <!INVISIBLE_REFERENCE!>C<!>