FIR visibility check: support private class members
This commit is contained in:
@@ -85,6 +85,7 @@ class FirCallResolver(
|
||||
typeArguments,
|
||||
session,
|
||||
file,
|
||||
transformer.components.implicitReceiverStack,
|
||||
transformer.components.container
|
||||
) { it.resultType }
|
||||
towerResolver.reset()
|
||||
@@ -143,6 +144,7 @@ class FirCallResolver(
|
||||
emptyList(),
|
||||
session,
|
||||
file,
|
||||
transformer.components.implicitReceiverStack,
|
||||
transformer.components.container
|
||||
) { it.resultType }
|
||||
towerResolver.reset()
|
||||
@@ -322,6 +324,7 @@ class FirCallResolver(
|
||||
emptyList(),
|
||||
session,
|
||||
file,
|
||||
transformer.components.implicitReceiverStack,
|
||||
transformer.components.container,
|
||||
expectedType,
|
||||
outerConstraintSystemBuilder,
|
||||
|
||||
@@ -8,11 +8,13 @@ package org.jetbrains.kotlin.fir.resolve.calls
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
|
||||
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
|
||||
import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS
|
||||
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStack
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
@@ -34,7 +36,8 @@ class CallInfo(
|
||||
val typeArguments: List<FirTypeProjection>,
|
||||
val session: FirSession,
|
||||
val containingFile: FirFile,
|
||||
val container: FirDeclaration,
|
||||
val implicitReceiverStack: ImplicitReceiverStack,
|
||||
val containingDeclaration: FirDeclaration,
|
||||
|
||||
// Three properties for callable references only
|
||||
val expectedType: ConeKotlinType? = null,
|
||||
|
||||
+2
-1
@@ -98,7 +98,8 @@ class InvokeReceiverCandidateCollector(
|
||||
invokeCallInfo.typeArguments,
|
||||
session,
|
||||
invokeCallInfo.containingFile,
|
||||
invokeCallInfo.container,
|
||||
invokeCallInfo.implicitReceiverStack,
|
||||
invokeCallInfo.containingDeclaration,
|
||||
invokeCallInfo.expectedType,
|
||||
invokeCallInfo.outerCSBuilder,
|
||||
invokeCallInfo.lhs,
|
||||
|
||||
+2
-1
@@ -71,7 +71,8 @@ fun createFunctionConsumer(
|
||||
callInfo.typeArguments,
|
||||
bodyResolveComponents.session,
|
||||
callInfo.containingFile,
|
||||
callInfo.container,
|
||||
callInfo.implicitReceiverStack,
|
||||
callInfo.containingDeclaration,
|
||||
callInfo.expectedType,
|
||||
callInfo.outerCSBuilder,
|
||||
callInfo.lhs,
|
||||
|
||||
@@ -9,19 +9,18 @@ import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS
|
||||
import org.jetbrains.kotlin.fir.resolve.createFunctionalType
|
||||
import org.jetbrains.kotlin.fir.resolve.createKPropertyType
|
||||
import org.jetbrains.kotlin.fir.resolve.firProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.*
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.coneTypeSafe
|
||||
import org.jetbrains.kotlin.load.java.JavaVisibilities
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
|
||||
@@ -275,81 +274,115 @@ internal object CheckVisibility : CheckerStage() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun ImplicitReceiverStack.canSeePrivateMemberOf(ownerId: ClassId): Boolean {
|
||||
for (implicitReceiverValue in receiversAsReversed()) {
|
||||
if (implicitReceiverValue !is ImplicitDispatchReceiverValue) continue
|
||||
if (implicitReceiverValue.boundSymbol.classId == ownerId) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun ClassId.asLocal(): ClassId = ClassId(packageFqName, relativeClassName, true)
|
||||
|
||||
private suspend fun checkVisibility(
|
||||
declaration: FirMemberDeclaration,
|
||||
symbol: AbstractFirBasedSymbol<*>,
|
||||
sink: CheckerSink,
|
||||
callInfo: CallInfo
|
||||
): Boolean {
|
||||
val useSiteFile = callInfo.containingFile
|
||||
val implicitReceiverStack = callInfo.implicitReceiverStack
|
||||
val visible = when (declaration.visibility) {
|
||||
JavaVisibilities.PACKAGE_VISIBILITY -> {
|
||||
symbol.packageFqName() == useSiteFile.packageFqName
|
||||
}
|
||||
Visibilities.PRIVATE, Visibilities.PRIVATE_TO_THIS -> {
|
||||
if (declaration.session == callInfo.session) {
|
||||
val provider = callInfo.session.firProvider
|
||||
when (symbol) {
|
||||
is FirClassLikeSymbol<*> -> {
|
||||
val classId = symbol.classId
|
||||
val ownerId = classId.outerClassId
|
||||
when {
|
||||
classId.isLocal -> {
|
||||
// Normally should not be here
|
||||
implicitReceiverStack.canSeePrivateMemberOf(ownerId?.asLocal() ?: classId)
|
||||
}
|
||||
ownerId == null -> {
|
||||
// Top-level: visible in file
|
||||
provider.getFirClassifierContainerFile(classId) == useSiteFile
|
||||
}
|
||||
else -> {
|
||||
// Member: visible inside parent class, including all its member classes
|
||||
implicitReceiverStack.canSeePrivateMemberOf(ownerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
is FirCallableSymbol<*> -> {
|
||||
val candidateFile = provider.getFirCallableContainerFile(symbol)
|
||||
val ownerId = symbol.callableId.classId
|
||||
when {
|
||||
candidateFile == null -> {
|
||||
// Local
|
||||
ownerId != null && implicitReceiverStack.canSeePrivateMemberOf(ownerId.asLocal())
|
||||
}
|
||||
ownerId == null -> {
|
||||
// Top-level: visible in file
|
||||
candidateFile == useSiteFile
|
||||
}
|
||||
else -> {
|
||||
// Member: visible inside parent class, including all its member classes
|
||||
implicitReceiverStack.canSeePrivateMemberOf(ownerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw AssertionError("Unsupported visibility check for ${declaration.javaClass}: ${declaration.render()}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Visibilities.INTERNAL -> {
|
||||
declaration.session == callInfo.session
|
||||
}
|
||||
Visibilities.PROTECTED -> {
|
||||
true // TODO: Support protected visibility
|
||||
}
|
||||
JavaVisibilities.PROTECTED_AND_PACKAGE -> {
|
||||
if (symbol.packageFqName() == useSiteFile.packageFqName) {
|
||||
true
|
||||
} else {
|
||||
true // TODO: Support protected visibility
|
||||
}
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
sink.yieldApplicability(CandidateApplicability.HIDDEN)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
|
||||
val symbol = candidate.symbol
|
||||
val declaration = symbol.fir
|
||||
if (declaration is FirMemberDeclaration) {
|
||||
val useSiteFile = callInfo.containingFile
|
||||
val visible = when (declaration.visibility) {
|
||||
JavaVisibilities.PACKAGE_VISIBILITY -> {
|
||||
symbol.packageFqName() == useSiteFile.packageFqName
|
||||
}
|
||||
Visibilities.PRIVATE, Visibilities.PRIVATE_TO_THIS -> {
|
||||
if (declaration.session == callInfo.session) {
|
||||
val provider = callInfo.session.firProvider
|
||||
when (symbol) {
|
||||
is FirClassLikeSymbol<*> -> {
|
||||
val classId = symbol.classId
|
||||
when {
|
||||
classId.isLocal -> {
|
||||
// Normally should not be here
|
||||
TODO()
|
||||
}
|
||||
!classId.isNestedClass -> {
|
||||
// Top-level: visible in file
|
||||
provider.getFirClassifierContainerFile(classId) == useSiteFile
|
||||
}
|
||||
else -> {
|
||||
// Member: visible inside parent class, including all its member classes
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
is FirCallableSymbol<*> -> {
|
||||
val candidateFile = provider.getFirCallableContainerFile(symbol)
|
||||
when {
|
||||
candidateFile == null -> {
|
||||
// Local
|
||||
TODO()
|
||||
}
|
||||
symbol.callableId.classId == null -> {
|
||||
// Top-level: visible in file
|
||||
candidateFile == useSiteFile
|
||||
}
|
||||
else -> {
|
||||
// Member: visible inside parent class, including all its member classes
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw AssertionError("Unsupported visibility check for ${declaration.javaClass}: ${declaration.render()}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Visibilities.INTERNAL -> {
|
||||
declaration.session == callInfo.session
|
||||
}
|
||||
Visibilities.PROTECTED -> {
|
||||
// Support protected visibility...
|
||||
TODO()
|
||||
}
|
||||
JavaVisibilities.PROTECTED_AND_PACKAGE -> {
|
||||
if (symbol.packageFqName() == useSiteFile.packageFqName) {
|
||||
true
|
||||
} else {
|
||||
// Support protected visibility...
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
else -> true
|
||||
if (!checkVisibility(declaration, symbol, sink, callInfo)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
sink.yieldApplicability(CandidateApplicability.HIDDEN)
|
||||
if (declaration is FirConstructor) {
|
||||
val ownerClassId = declaration.symbol.callableId.classId!!
|
||||
val provider = declaration.session.firSymbolProvider
|
||||
val classSymbol = provider.getClassLikeSymbolByFqName(ownerClassId)
|
||||
|
||||
if (classSymbol is FirRegularClassSymbol) {
|
||||
checkVisibility(classSymbol.fir, classSymbol, sink, callInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -131,7 +131,8 @@ class FirSyntheticCallGenerator(
|
||||
typeArguments = emptyList(),
|
||||
session = session,
|
||||
containingFile = file,
|
||||
container = container
|
||||
implicitReceiverStack = implicitReceiverStack,
|
||||
containingDeclaration = container
|
||||
) { it.resultType }
|
||||
|
||||
private fun generateSyntheticSelectFunction(callableId: CallableId, isVararg: Boolean = true): FirSimpleFunctionImpl {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// FILE: Node.java
|
||||
|
||||
public interface Node<R> {
|
||||
R result();
|
||||
}
|
||||
|
||||
// FILE: foo.kt
|
||||
|
||||
fun foo(): Boolean {
|
||||
object : Node<Boolean> {
|
||||
private var result = false
|
||||
|
||||
fun bar(): Boolean {
|
||||
return !result
|
||||
}
|
||||
|
||||
// Cannot see private member of local class / anonymous object
|
||||
override fun result() = result
|
||||
}
|
||||
|
||||
val some = true
|
||||
return !some
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
FILE: foo.kt
|
||||
public final fun foo(): R|kotlin/Boolean| {
|
||||
object : R|Node<kotlin/Boolean>| {
|
||||
private constructor(): R|kotlin/Any| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
private final var result: R|kotlin/Boolean| = Boolean(false)
|
||||
private get(): R|kotlin/Boolean|
|
||||
private set(value: R|kotlin/Boolean|): R|kotlin/Unit|
|
||||
|
||||
public final fun bar(): R|kotlin/Boolean| {
|
||||
^bar R|/anonymous.result|.R|kotlin/Boolean.not|()
|
||||
}
|
||||
|
||||
public final override fun result(): R|kotlin/Boolean| {
|
||||
^result R|/anonymous.result|
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lval some: R|kotlin/Boolean| = Boolean(true)
|
||||
^foo R|<local>/some|.R|kotlin/Boolean.not|()
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// FILE: first.kt
|
||||
|
||||
private fun foo() {}
|
||||
|
||||
private class Private {
|
||||
private fun bar() {}
|
||||
|
||||
fun baz() {
|
||||
bar()
|
||||
Nested()
|
||||
}
|
||||
|
||||
inner class Inner {
|
||||
fun foo() {
|
||||
bar()
|
||||
}
|
||||
}
|
||||
|
||||
private class Nested
|
||||
}
|
||||
|
||||
fun withLocals() {
|
||||
class Local {
|
||||
private fun bar()
|
||||
|
||||
fun baz() {
|
||||
bar()
|
||||
Inner()
|
||||
}
|
||||
|
||||
private inner class Inner {
|
||||
fun foo() {
|
||||
bar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Local().baz()
|
||||
|
||||
Local().<!INAPPLICABLE_CANDIDATE!>bar<!>() // hidden
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo()
|
||||
Private().baz()
|
||||
Private().Inner()
|
||||
|
||||
Private().<!INAPPLICABLE_CANDIDATE!>bar<!>() // hidden
|
||||
Private.<!INAPPLICABLE_CANDIDATE!>Nested<!>() // hidden
|
||||
}
|
||||
|
||||
// FILE: second.kt
|
||||
|
||||
fun secondTest() {
|
||||
<!INAPPLICABLE_CANDIDATE!>foo<!>() // hidden
|
||||
<!INAPPLICABLE_CANDIDATE!>Private<!>() // hidden
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
FILE: first.kt
|
||||
private final fun foo(): R|kotlin/Unit| {
|
||||
}
|
||||
private final class Private : R|kotlin/Any| {
|
||||
public constructor(): R|Private| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
private final fun bar(): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
public final fun baz(): R|kotlin/Unit| {
|
||||
this@R|/Private|.R|/Private.bar|()
|
||||
R|/Private.Nested.Nested|()
|
||||
}
|
||||
|
||||
public final inner class Inner : R|kotlin/Any| {
|
||||
public constructor(): R|Private.Inner| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
this@R|/Private|.R|/Private.bar|()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class Nested : R|kotlin/Any| {
|
||||
public constructor(): R|Private.Nested| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
public final fun withLocals(): R|kotlin/Unit| {
|
||||
local final class Local : R|kotlin/Any| {
|
||||
public constructor(): R|Local| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
private final fun bar(): R|kotlin/Unit|
|
||||
|
||||
public final fun baz(): R|kotlin/Unit| {
|
||||
R|/Local.bar|()
|
||||
R|/Local.Inner.Inner|()
|
||||
}
|
||||
|
||||
local final inner class Inner : R|kotlin/Any| {
|
||||
public constructor(): R|Local.Inner| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
R|/Local.bar|()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
R|/Local.Local|().R|/Local.baz|()
|
||||
R|/Local.Local|().<Inapplicable(HIDDEN): [/Local.bar]>#()
|
||||
}
|
||||
public final fun test(): R|kotlin/Unit| {
|
||||
R|/foo|()
|
||||
R|/Private.Private|().R|/Private.baz|()
|
||||
R|/Private.Private|().R|/Private.Inner.Inner|()
|
||||
R|/Private.Private|().<Inapplicable(HIDDEN): [/Private.bar]>#()
|
||||
Q|Private|.<Inapplicable(HIDDEN): [/Private.Nested.Nested]>#()
|
||||
}
|
||||
FILE: second.kt
|
||||
public final fun secondTest(): R|kotlin/Unit| {
|
||||
<Inapplicable(HIDDEN): [/foo]>#()
|
||||
<Inapplicable(HIDDEN): [/Private.Private]>#()
|
||||
}
|
||||
+10
@@ -414,6 +414,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/localTypes.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("localWithBooleanNot.kt")
|
||||
public void testLocalWithBooleanNot() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/localWithBooleanNot.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberExtension.kt")
|
||||
public void testMemberExtension() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/memberExtension.kt");
|
||||
@@ -439,6 +444,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/privateObjectLiteral.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("privateVisibility.kt")
|
||||
public void testPrivateVisibility() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/privateVisibility.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("qualifiedExpressions.kt")
|
||||
public void testQualifiedExpressions() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/qualifiedExpressions.kt");
|
||||
|
||||
@@ -100,7 +100,7 @@ FILE fqName:<root> fileName:/objectAsCallable.kt
|
||||
PROPERTY name:test1 visibility:public modality:FINAL [val]
|
||||
FIELD PROPERTY_BACKING_FIELD name:test1 type:IrErrorType visibility:private [final,static]
|
||||
EXPRESSION_BODY
|
||||
ERROR_CALL 'Unresolved reference: <Inapplicable(PARAMETER_MAPPING_ERROR): [/A.A]>#' type=IrErrorType
|
||||
ERROR_CALL 'Unresolved reference: <Inapplicable(HIDDEN): [/A.A]>#' type=IrErrorType
|
||||
CONST Int type=kotlin.Int value=42
|
||||
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test1> visibility:public modality:FINAL <> () returnType:IrErrorType
|
||||
correspondingProperty: PROPERTY name:test1 visibility:public modality:FINAL [val]
|
||||
|
||||
Reference in New Issue
Block a user