[FIR] Check standalone companion object qualifiers are visible
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
+2
-6
@@ -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
|
||||
|
||||
+29
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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.
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+2
-2
@@ -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<!>
|
||||
|
||||
Reference in New Issue
Block a user