[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? {
|
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
|
||||||
|
}
|
||||||
|
|||||||
+2
-6
@@ -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
|
||||||
|
|||||||
+29
-4
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -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.
|
||||||
|
|||||||
+1
-1
@@ -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)
|
||||||
|
|||||||
+2
-2
@@ -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<!>
|
||||||
|
|||||||
Reference in New Issue
Block a user