FIR IDE: Add visibility checks to the completion

Visibility checks currently do not work in some cases, for example:

- public nested class of private class is seen globally
- private_for_this does not work
- some other cases I am not yet aware of

If `FirVisibilityChecker.isVisible` fix those issues, they will be fixed
automatically in the completion
This commit is contained in:
Roman Golyshev
2021-03-30 14:48:38 +03:00
committed by Space
parent 78b2324a51
commit e0fca9d2f6
24 changed files with 303 additions and 32 deletions
@@ -1,3 +1,4 @@
// FIR_COMPARISON
object O {
private val zzzz = 0
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class Some() {
public val testPublic = 12
protected val testProtected = 12
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class Some() {
public val testPublic = 12
protected val testProtected = 12
@@ -0,0 +1,19 @@
// FIR_COMPARISON
package test
open class Base {
public val testPublic = 12
protected val testProtected = 12
private val testPrivate = 12
val testPackage = 12
fun baseClassMember() {
class Local {
init {
<caret>
}
}
}
}
// EXIST: testPublic, testProtected, testPackage, testPrivate
@@ -0,0 +1,19 @@
// FIR_COMPARISON
package test
open class Base {
public val testPublic = 12
protected val testProtected = 12
private val testPrivate = 12
val testPackage = 12
fun baseClassMember() {
class Local {
fun localClassMemeber() {
<caret>
}
}
}
}
// EXIST: testPublic, testProtected, testPackage, testPrivate
@@ -0,0 +1,27 @@
// FIR_COMPARISON
package test
open class BaseClass {
companion object {
val publicVal = 10
fun publicFun() {}
protected val protectedVal = 30
protected fun protectedFun() {}
private val privateVal = 30
private fun privateFun() {}
}
}
fun test() {
BaseClass.<caret>
}
// EXIST: publicVal
// EXIST: publicFun
// ABSENT: protectedVal
// ABSENT: protectedFun
// ABSENT: privateVal
// ABSENT: privateFun
@@ -0,0 +1,29 @@
// FIR_COMPARISON
package test
open class BaseClass {
companion object {
val publicVal = 10
fun publicFun() {}
protected val protectedVal = 30
protected fun protectedFun() {}
private val privateVal = 30
private fun privateFun() {}
}
}
class Subclass : BaseClass() {
fun test() {
BaseClass.<caret>
}
}
// EXIST: publicVal
// EXIST: publicFun
// EXIST: protectedVal
// EXIST: protectedFun
// ABSENT: privateVal
// ABSENT: privateFun
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(a: java.lang.Thread) {
a.na<caret>
}
@@ -0,0 +1,13 @@
package dependency
class PublicTopLevelClass {
class PublicNestedClass
protected class ProtectedNestedClass
private class PrivateNestedClass
inner class PublicInnerClass
protected inner class ProtectedInnerClass
private inner class PrivateInnerClass
}
private class PrivateTopLevelClass
@@ -0,0 +1,7 @@
// FIR_COMPARISON
package test
fun usage(): P<caret> {}
// EXIST: PublicTopLevelClass, PublicNestedClass, PublicInnerClass
// ABSENT: PrivateNestedClass, PrivateInnerClass, ProtectedNestedClass, ProtectedInnerClass, PrivateTopLevelClass
@@ -3,4 +3,6 @@ package abc
val String.helloProp1: Int get() = 1
val String.helloProp2: Int get() = 2
val Int.helloProp3: Int get() = 3
val helloProp4: Int = 4
val helloProp4: Int = 4
private val String.helloPropPrivate get() = 1
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package first
fun firstFun() {
@@ -5,8 +6,9 @@ fun firstFun() {
a.hello<caret>
}
// EXIST: { lookupString: "helloProp1", attributes: "bold" }
// EXIST: { lookupString: "helloProp2", attributes: "bold" }
// EXIST: helloProp1
// EXIST: helloProp2
// ABSENT: helloProp3
// ABSENT: helloProp4
// ABSENT: helloPropPrivate
// NOTHING_ELSE
@@ -6,3 +6,5 @@ fun globalFun2(): Int = 1
object Some {
fun globalFun3(): Int = 3
}
private fun globalFunPrivate() {}
@@ -7,3 +7,4 @@ fun testFun() {
// EXIST: globalFun1, globalFun2
// ABSENT: globalFun3
// ABSENT: globalFunPrivate
@@ -0,0 +1,11 @@
package dependency
private class PrivateTopLevelClass {
class PublicNestedClass
protected class ProtectedNestedClass
private class PrivateNestedClass
inner class PublicInnerClass
protected inner class ProtectedInnerClass
private inner class PrivateInnerClass
}
@@ -0,0 +1,13 @@
package test
fun usage(): P<caret> {}
// ABSENT: PrivateTopLevelClass
// ABSENT: PublicNestedClass
// ABSENT: ProtectedNestedClass
// ABSENT: PrivateNestedClass
// ABSENT: PublicInnerClass
// ABSENT: ProtectedInnerClass
// ABSENT: PrivateInnerClass
@@ -1,4 +1,6 @@
package first
fun secondFun() {
}
}
private fun secondFunPrivate() {}
@@ -5,4 +5,5 @@ fun firstFun() {
s<caret>
}
// EXIST: secondFun
// EXIST: secondFun
// ABSENT: secondFunPrivate
@@ -2882,6 +2882,26 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityInSubclassForce.kt");
}
@TestMetadata("VisibilityOfClassMembersFromLocalClassConstructor.kt")
public void testVisibilityOfClassMembersFromLocalClassConstructor() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfClassMembersFromLocalClassConstructor.kt");
}
@TestMetadata("VisibilityOfClassMembersFromLocalClassMember.kt")
public void testVisibilityOfClassMembersFromLocalClassMember() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfClassMembersFromLocalClassMember.kt");
}
@TestMetadata("VisibilityOfCompanionObjectMembersFromOutside.kt")
public void testVisibilityOfCompanionObjectMembersFromOutside() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfCompanionObjectMembersFromOutside.kt");
}
@TestMetadata("VisibilityOfCompanionObjectMembersFromSubclass.kt")
public void testVisibilityOfCompanionObjectMembersFromSubclass() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfCompanionObjectMembersFromSubclass.kt");
}
@TestMetadata("VisibilityPrivateToThis.kt")
public void testVisibilityPrivateToThis() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityPrivateToThis.kt");
@@ -2882,6 +2882,26 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityInSubclassForce.kt");
}
@TestMetadata("VisibilityOfClassMembersFromLocalClassConstructor.kt")
public void testVisibilityOfClassMembersFromLocalClassConstructor() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfClassMembersFromLocalClassConstructor.kt");
}
@TestMetadata("VisibilityOfClassMembersFromLocalClassMember.kt")
public void testVisibilityOfClassMembersFromLocalClassMember() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfClassMembersFromLocalClassMember.kt");
}
@TestMetadata("VisibilityOfCompanionObjectMembersFromOutside.kt")
public void testVisibilityOfCompanionObjectMembersFromOutside() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfCompanionObjectMembersFromOutside.kt");
}
@TestMetadata("VisibilityOfCompanionObjectMembersFromSubclass.kt")
public void testVisibilityOfCompanionObjectMembersFromSubclass() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfCompanionObjectMembersFromSubclass.kt");
}
@TestMetadata("VisibilityPrivateToThis.kt")
public void testVisibilityPrivateToThis() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityPrivateToThis.kt");
@@ -224,6 +224,11 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
runTest("idea/idea-completion/testData/basic/multifile/NoGenericFunDuplication/");
}
@TestMetadata("NotImportedClass")
public void testNotImportedClass() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedClass/");
}
@TestMetadata("NotImportedExtensionForImplicitReceiver")
public void testNotImportedExtensionForImplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedExtensionForImplicitReceiver/");
@@ -269,6 +274,11 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
runTest("idea/idea-completion/testData/basic/multifile/NotImportedJavaClass/");
}
@TestMetadata("NotImportedNestedClassFromPrivateClass")
public void testNotImportedNestedClassFromPrivateClass() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedNestedClassFromPrivateClass/");
}
@TestMetadata("NotImportedObject")
public void testNotImportedObject() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedObject/");
@@ -21,10 +21,9 @@ import org.jetbrains.kotlin.idea.frontend.api.analyseInFakeAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtCompositeScope
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScopeNameFilter
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithVisibility
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.isExtension
import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
@@ -86,6 +85,18 @@ private fun interface ExtensionApplicabilityChecker {
fun isApplicable(symbol: KtCallableSymbol): Boolean
}
private fun interface CompletionVisibilityChecker {
fun isVisible(symbol: KtSymbolWithVisibility): Boolean
fun isVisible(symbol: KtCallableSymbol): Boolean {
return symbol !is KtSymbolWithVisibility || isVisible(symbol as KtSymbolWithVisibility)
}
fun isVisible(symbol: KtClassifierSymbol): Boolean {
return symbol !is KtSymbolWithVisibility || isVisible(symbol as KtSymbolWithVisibility)
}
}
/**
* Currently, this class is responsible for collecting all possible completion variants.
*
@@ -138,6 +149,7 @@ private class KotlinCommonCompletionProvider(
val explicitReceiver = nameExpression.getReceiverExpression()
analyseInFakeAnalysisSession(originalFile, nameExpression) {
val fileSymbol = originalFile.getFileSymbol()
val expectedType = nameExpression.getExpectedType()
val scopesContext = originalFile.getScopeContextForPosition(nameExpression)
@@ -146,16 +158,24 @@ private class KotlinCommonCompletionProvider(
it.checkExtensionIsSuitable(originalFile, nameExpression, explicitReceiver)
}
val visibilityChecker = CompletionVisibilityChecker {
parameters.invocationCount > 1 || isVisible(it, fileSymbol, explicitReceiver, parameters.position)
}
when {
nameExpression.parent is KtUserType -> collectTypesCompletion(result, scopesContext.scopes, expectedType)
explicitReceiver != null -> collectDotCompletion(
result,
scopesContext.scopes,
explicitReceiver,
expectedType,
extensionChecker
)
else -> collectDefaultCompletion(result, scopesContext, expectedType, extensionChecker)
nameExpression.parent is KtUserType -> collectTypesCompletion(result, scopesContext.scopes, expectedType, visibilityChecker)
explicitReceiver != null -> {
collectDotCompletion(
result,
scopesContext.scopes,
explicitReceiver,
expectedType,
extensionChecker,
visibilityChecker,
)
}
else -> collectDefaultCompletion(result, scopesContext, expectedType, extensionChecker, visibilityChecker)
}
}
}
@@ -164,12 +184,19 @@ private class KotlinCommonCompletionProvider(
result: CompletionResultSet,
implicitScopes: KtScope,
expectedType: KtType?,
visibilityChecker: CompletionVisibilityChecker,
) {
val classesFromScopes = implicitScopes.getClassifierSymbols(scopeNameFilter)
val classesFromScopes = implicitScopes
.getClassifierSymbols(scopeNameFilter)
.filter { visibilityChecker.isVisible(it) }
classesFromScopes.forEach { addSymbolToCompletion(result, expectedType, it) }
val kotlinClassesFromIndices = indexHelper.getKotlinClasses(scopeNameFilter, psiFilter = { it !is KtEnumEntry })
kotlinClassesFromIndices.forEach { addSymbolToCompletion(result, expectedType, it.getSymbol()) }
kotlinClassesFromIndices.asSequence()
.map { it.getSymbol() as KtClassifierSymbol }
.filter { visibilityChecker.isVisible(it) }
.forEach { addSymbolToCompletion(result, expectedType, it) }
}
private fun KtAnalysisSession.collectDotCompletion(
@@ -178,19 +205,19 @@ private class KotlinCommonCompletionProvider(
explicitReceiver: KtExpression,
expectedType: KtType?,
extensionChecker: ExtensionApplicabilityChecker,
visibilityChecker: CompletionVisibilityChecker,
) {
val typeOfPossibleReceiver = explicitReceiver.getKtType()
val possibleReceiverScope = typeOfPossibleReceiver.getTypeScope() ?: return
val nonExtensionMembers = possibleReceiverScope.collectNonExtensions()
val extensionNonMembers = implicitScopes.collectSuitableExtensions(extensionChecker)
val nonExtensionMembers = possibleReceiverScope.collectNonExtensions(visibilityChecker)
val extensionNonMembers = implicitScopes.collectSuitableExtensions(extensionChecker, visibilityChecker)
nonExtensionMembers.forEach { addSymbolToCompletion(result, expectedType, it) }
extensionNonMembers.forEach { addSymbolToCompletion(result, expectedType, it) }
collectTopLevelExtensionsFromIndices(listOf(typeOfPossibleReceiver), extensionChecker)
collectTopLevelExtensionsFromIndices(listOf(typeOfPossibleReceiver), extensionChecker, visibilityChecker)
.forEach { addSymbolToCompletion(result, expectedType, it) }
}
private fun KtAnalysisSession.collectDefaultCompletion(
@@ -198,11 +225,12 @@ private class KotlinCommonCompletionProvider(
implicitScopesContext: KtScopeContext,
expectedType: KtType?,
extensionChecker: ExtensionApplicabilityChecker,
visibilityChecker: CompletionVisibilityChecker,
) {
val (implicitScopes, implicitReceiversTypes) = implicitScopesContext
val availableNonExtensions = implicitScopes.collectNonExtensions()
val extensionsWhichCanBeCalled = implicitScopes.collectSuitableExtensions(extensionChecker)
val availableNonExtensions = implicitScopes.collectNonExtensions(visibilityChecker)
val extensionsWhichCanBeCalled = implicitScopes.collectSuitableExtensions(extensionChecker, visibilityChecker)
availableNonExtensions.forEach { addSymbolToCompletion(result, expectedType, it) }
extensionsWhichCanBeCalled.forEach { addSymbolToCompletion(result, expectedType, it) }
@@ -210,33 +238,44 @@ private class KotlinCommonCompletionProvider(
if (shouldCompleteTopLevelCallablesFromIndex) {
val topLevelCallables = indexHelper.getTopLevelCallables(scopeNameFilter)
topLevelCallables.asSequence()
.map { it.getSymbol() }
.map { it.getSymbol() as KtCallableSymbol }
.filter { visibilityChecker.isVisible(it) }
.forEach { addSymbolToCompletion(result, expectedType, it) }
}
collectTopLevelExtensionsFromIndices(implicitReceiversTypes, extensionChecker)
collectTopLevelExtensionsFromIndices(implicitReceiversTypes, extensionChecker, visibilityChecker)
.forEach { addSymbolToCompletion(result, expectedType, it) }
collectTypesCompletion(result, implicitScopes, expectedType)
collectTypesCompletion(result, implicitScopes, expectedType, visibilityChecker)
}
private fun KtAnalysisSession.collectTopLevelExtensionsFromIndices(
receiverTypes: List<KtType>,
extensionChecker: ExtensionApplicabilityChecker,
visibilityChecker: CompletionVisibilityChecker,
): Sequence<KtCallableSymbol> {
val implicitReceiverNames = findAllNamesOfTypes(receiverTypes)
val topLevelExtensions = indexHelper.getTopLevelExtensions(scopeNameFilter, implicitReceiverNames)
return topLevelExtensions.asSequence()
.map { it.getSymbol() as KtCallableSymbol }
.filter { visibilityChecker.isVisible(it) }
.filter { extensionChecker.isApplicable(it) }
}
private fun KtScope.collectNonExtensions(): Sequence<KtCallableSymbol> =
getCallableSymbols(scopeNameFilter).filterNot { it.isExtension }
private fun KtScope.collectNonExtensions(visibilityChecker: CompletionVisibilityChecker) =
getCallableSymbols(scopeNameFilter)
.filterNot { it.isExtension }
.filter { visibilityChecker.isVisible(it) }
private fun KtScope.collectSuitableExtensions(extensionChecker: ExtensionApplicabilityChecker): Sequence<KtCallableSymbol> =
getCallableSymbols(scopeNameFilter).filter { it.isExtension && extensionChecker.isApplicable(it) }
private fun KtCompositeScope.collectSuitableExtensions(
hasSuitableExtensionReceiver: ExtensionApplicabilityChecker,
visibilityChecker: CompletionVisibilityChecker,
): Sequence<KtCallableSymbol> =
getCallableSymbols(scopeNameFilter)
.filter { it.isExtension }
.filter { visibilityChecker.isVisible(it) }
.filter { hasSuitableExtensionReceiver.isApplicable(it) }
private fun KtAnalysisSession.findAllNamesOfTypes(implicitReceiversTypes: List<KtType>) =
implicitReceiversTypes.flatMapTo(hashSetOf()) { with(typeNamesProvider) { findAllNames(it) } }
@@ -2882,6 +2882,26 @@ public class HighLevelJvmBasicCompletionTestGenerated extends AbstractHighLevelJ
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityInSubclassForce.kt");
}
@TestMetadata("VisibilityOfClassMembersFromLocalClassConstructor.kt")
public void testVisibilityOfClassMembersFromLocalClassConstructor() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfClassMembersFromLocalClassConstructor.kt");
}
@TestMetadata("VisibilityOfClassMembersFromLocalClassMember.kt")
public void testVisibilityOfClassMembersFromLocalClassMember() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfClassMembersFromLocalClassMember.kt");
}
@TestMetadata("VisibilityOfCompanionObjectMembersFromOutside.kt")
public void testVisibilityOfCompanionObjectMembersFromOutside() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfCompanionObjectMembersFromOutside.kt");
}
@TestMetadata("VisibilityOfCompanionObjectMembersFromSubclass.kt")
public void testVisibilityOfCompanionObjectMembersFromSubclass() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityOfCompanionObjectMembersFromSubclass.kt");
}
@TestMetadata("VisibilityPrivateToThis.kt")
public void testVisibilityPrivateToThis() throws Exception {
runTest("idea/idea-completion/testData/basic/common/visibility/VisibilityPrivateToThis.kt");
@@ -224,6 +224,11 @@ public class HighLevelMultiFileJvmBasicCompletionTestGenerated extends AbstractH
runTest("idea/idea-completion/testData/basic/multifile/NoGenericFunDuplication/");
}
@TestMetadata("NotImportedClass")
public void testNotImportedClass() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedClass/");
}
@TestMetadata("NotImportedExtensionForImplicitReceiver")
public void testNotImportedExtensionForImplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedExtensionForImplicitReceiver/");
@@ -269,6 +274,11 @@ public class HighLevelMultiFileJvmBasicCompletionTestGenerated extends AbstractH
runTest("idea/idea-completion/testData/basic/multifile/NotImportedJavaClass/");
}
@TestMetadata("NotImportedNestedClassFromPrivateClass")
public void testNotImportedNestedClassFromPrivateClass() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedNestedClassFromPrivateClass/");
}
@TestMetadata("NotImportedObject")
public void testNotImportedObject() throws Exception {
runTest("idea/idea-completion/testData/basic/multifile/NotImportedObject/");