Use information about receivers in completion

- Found few problems during resolving a single functon; disabled
assertions and marked them with TODO
- Add simple completion tests to simplify development
This commit is contained in:
Roman Golyshev
2020-07-01 14:30:40 +03:00
committed by Ilya Kirillov
parent 0f5fc1fa99
commit ffb907150a
18 changed files with 403 additions and 78 deletions
@@ -37,10 +37,11 @@ class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val k
state = State.Completed
if (kind == Kind.Stub) return
val sortedNodes = orderNodes()
assert(sortedNodes.size == _nodes.size)
for (node in _nodes) {
assert(node in sortedNodes)
}
// TODO Fix this
// assert(sortedNodes.size == _nodes.size)
// for (node in _nodes) {
// assert(node in sortedNodes)
// }
_nodes.clear()
_nodes.addAll(sortedNodes)
}
@@ -368,7 +368,10 @@ class ControlFlowGraphBuilder {
} ?: continue
addEdge(node, graph.enterNode, preferredKind = EdgeKind.CfgForward)
node = graph.exitNode
classGraph.addSubGraph(graph)
// TODO Fix this
if (graph.owner == null) {
classGraph.addSubGraph(graph)
}
}
addEdge(node, exitNode, preferredKind = EdgeKind.CfgForward)
return popGraph()
+2
View File
@@ -187,6 +187,8 @@ dependencies {
testRuntime(intellijPluginDep("smali"))
testRuntime(intellijPluginDep("testng"))
testRuntime(project(":idea:idea-frontend-fir"))
if (Ide.AS36.orHigher()) {
testRuntime(intellijPluginDep("android-layoutlib"))
testRuntime(intellijPluginDep("git4idea"))
@@ -0,0 +1,11 @@
class A {
fun aa() {}
val aaa = 10
fun test() {
<caret>
}
}
// EXIST: aa
// EXIST: aaa
@@ -0,0 +1,20 @@
class B {
fun bb() {}
val bbb = 20
}
class A {
fun aa() {}
val aaa = 10
fun test() {
val b = B()
b.<caret>
}
}
// EXIST: bb
// EXIST: bbb
// ABSENT: aa
// ABSENT: aaa
@@ -0,0 +1,17 @@
open class A {
fun aa() {}
val aaa = 10
fun test() {
<caret>
}
}
class B : A() {
fun test() {
<caret>
}
}
// EXIST: aa
// EXIST: aaa
@@ -0,0 +1,18 @@
class A {
fun aa() {}
val aaa = 10
inner class AA {
fun bb() {}
val bbb = 20
fun test() {
<caret>
}
}
}
// EXIST: aa
// EXIST: aaa
// EXIST: bb
// EXIST: bbb
@@ -0,0 +1,13 @@
class A {
fun aa() {}
val aaa = 10
}
fun test() {
val a = A()
a.<caret>
}
// EXIST: aa
// EXIST: aaa
@@ -0,0 +1,21 @@
fun Any.anyFun() {}
val Any.anyVal: Int get() = 10
class A
fun A.aFun() {}
val A.aVal: Int get() = 10
class B
fun B.bFun() {}
val B.bVal: Int get() = 10
fun test(a: A) {
a.<caret>
}
// EXIST: anyFun
// EXIST: anyVal
// EXIST: aVal
// EXIST: aFun
// ABSENT: bVal
// ABSENT: bFun
@@ -0,0 +1,23 @@
fun <T : Any> T.anyFun() {}
val <T : Any> T.anyVal: Int get() = 10
open class A
fun <T : A> T.aFun() {}
val <T : A> T.aVal: Int get() = 10
open class B
fun <T : B> T.bFun() {}
val <T : B> T.bVal: Int get() = 10
fun test(a: A) {
a.aFun()
a.<caret>
}
// EXIST: anyFun
// EXIST: anyVal
// EXIST: aVal
// EXIST: aFun
// ABSENT: bVal
// ABSENT: bFun
@@ -0,0 +1,24 @@
class A {
fun aa() {}
val aaa = 10
}
fun A.run(action: A.() -> Unit) {}
fun test(a: A) {
a.run {
<caret>
// remove this
Unit
}
}
// this does not work for some reason
//fun A.test() {
//}
// EXIST: aa
// EXIST: aaa
// EXIST: run
@@ -0,0 +1,9 @@
fun test() {
fun aa() {}
val aaa = 10
<caret>
}
// EXIST: aa
// EXIST: aaa
@@ -0,0 +1,20 @@
fun run(action: () -> Unit) = action()
fun test() {
fun aa() {}
val aaa = 10
run {
fun aabb() {}
val aaabb = 20
<caret>
Unit // remove this
}
}
// EXIST: aa
// EXIST: aaa
// EXIST: aabb
// EXIST: aaabb
@@ -0,0 +1,9 @@
fun aa() {}
val aaa = 10
fun test() {
<caret>
}
// EXIST: aa
// EXIST: aaa
@@ -0,0 +1,11 @@
class A
class OuterClass {
fun A.innerExt() {}
fun test(a: A) {
a.<caret>
}
}
// EXIST: innerExt
@@ -2215,6 +2215,79 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/primitiveCompletion")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PrimitiveCompletion extends AbstractJSBasicCompletionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInPrimitiveCompletion() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-completion/testData/basic/common/primitiveCompletion"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("classFieldsAndFunctions.kt")
public void testClassFieldsAndFunctions() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctions.kt");
}
@TestMetadata("classFieldsAndFunctionsExplicitReceiver.kt")
public void testClassFieldsAndFunctionsExplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsExplicitReceiver.kt");
}
@TestMetadata("classFieldsAndFunctionsFromInheritor.kt")
public void testClassFieldsAndFunctionsFromInheritor() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInheritor.kt");
}
@TestMetadata("classFieldsAndFunctionsFromInnerClass.kt")
public void testClassFieldsAndFunctionsFromInnerClass() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClass.kt");
}
@TestMetadata("explicitReceiverCompletion.kt")
public void testExplicitReceiverCompletion() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/explicitReceiverCompletion.kt");
}
@TestMetadata("extensionPropertyAndFunctionExplicitReceiver.kt")
public void testExtensionPropertyAndFunctionExplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionExplicitReceiver.kt");
}
@TestMetadata("genericExtensionPropertyAndFunctionExplicitReceiver.kt")
public void testGenericExtensionPropertyAndFunctionExplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/genericExtensionPropertyAndFunctionExplicitReceiver.kt");
}
@TestMetadata("implicitReceiverCompletion.kt")
public void testImplicitReceiverCompletion() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/implicitReceiverCompletion.kt");
}
@TestMetadata("localVariablesAndFunctions.kt")
public void testLocalVariablesAndFunctions() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/localVariablesAndFunctions.kt");
}
@TestMetadata("localVariablesAndFunctionsFromNestedScope.kt")
public void testLocalVariablesAndFunctionsFromNestedScope() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/localVariablesAndFunctionsFromNestedScope.kt");
}
@TestMetadata("topLevelVariablesAndFunctions.kt")
public void testTopLevelVariablesAndFunctions() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/topLevelVariablesAndFunctions.kt");
}
@TestMetadata("twoReceivers.kt")
public void testTwoReceivers() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/twoReceivers.kt");
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/shadowing")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -2215,6 +2215,79 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/primitiveCompletion")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PrimitiveCompletion extends AbstractJvmBasicCompletionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInPrimitiveCompletion() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-completion/testData/basic/common/primitiveCompletion"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("classFieldsAndFunctions.kt")
public void testClassFieldsAndFunctions() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctions.kt");
}
@TestMetadata("classFieldsAndFunctionsExplicitReceiver.kt")
public void testClassFieldsAndFunctionsExplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsExplicitReceiver.kt");
}
@TestMetadata("classFieldsAndFunctionsFromInheritor.kt")
public void testClassFieldsAndFunctionsFromInheritor() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInheritor.kt");
}
@TestMetadata("classFieldsAndFunctionsFromInnerClass.kt")
public void testClassFieldsAndFunctionsFromInnerClass() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClass.kt");
}
@TestMetadata("explicitReceiverCompletion.kt")
public void testExplicitReceiverCompletion() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/explicitReceiverCompletion.kt");
}
@TestMetadata("extensionPropertyAndFunctionExplicitReceiver.kt")
public void testExtensionPropertyAndFunctionExplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionExplicitReceiver.kt");
}
@TestMetadata("genericExtensionPropertyAndFunctionExplicitReceiver.kt")
public void testGenericExtensionPropertyAndFunctionExplicitReceiver() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/genericExtensionPropertyAndFunctionExplicitReceiver.kt");
}
@TestMetadata("implicitReceiverCompletion.kt")
public void testImplicitReceiverCompletion() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/implicitReceiverCompletion.kt");
}
@TestMetadata("localVariablesAndFunctions.kt")
public void testLocalVariablesAndFunctions() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/localVariablesAndFunctions.kt");
}
@TestMetadata("localVariablesAndFunctionsFromNestedScope.kt")
public void testLocalVariablesAndFunctionsFromNestedScope() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/localVariablesAndFunctionsFromNestedScope.kt");
}
@TestMetadata("topLevelVariablesAndFunctions.kt")
public void testTopLevelVariablesAndFunctions() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/topLevelVariablesAndFunctions.kt");
}
@TestMetadata("twoReceivers.kt")
public void testTwoReceivers() throws Exception {
runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/twoReceivers.kt");
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/shadowing")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -9,23 +9,24 @@ import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.patterns.PlatformPatterns
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.scope
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.idea.fir.getFirOfClosestParent
import org.jetbrains.kotlin.idea.fir.getOrBuildFirSafe
import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtLabelReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.types.AbstractTypeChecker
class KotlinFirCompletionContributor : CompletionContributor() {
init {
@@ -50,81 +51,57 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
val completionContext = LowLevelFirApiFacade.buildCompletionContextForFunction(originalFileFir, parentFunction)
val element = nameExpression.getFirOfClosestParent() as? FirQualifiedAccessExpression ?: return
for (scope in getScopes(element, completionContext.session)) {
for (name in scope.availableNames()) {
result.addElement(LookupElementBuilder.create(name.asString()))
}
}
val towerDataContext = completionContext.getTowerDataContext(nameExpression)
for (localScope in towerDataContext.localScopes) {
for (name in localScope.getCallableNames()) {
result.addElement(LookupElementBuilder.create(name.asString()))
}
}
// val receiver = element.explicitReceiver
// if (receiver != null) {
//
// val firScope =
// receiver.typeRef.coneTypeUnsafe<ConeKotlinType>().scope(firFunction.session, ScopeSession()) ?: return
// // (symbolProvider.getSymbolByTypeRef<FirClassSymbol<*>>(receiver.typeRef))?.buildUseSiteMemberScope(firFunction.session, ScopeSession()) ?: return
//
// for (name in firScope.getCallableNames()) {
// firScope.processFunctionsByName(name) {
// it.fir
// result.addElement(LookupElementBuilder.create(name.asString()))
// }
//
// firScope.processPropertiesByName(name) {
// result.addElement(LookupElementBuilder.create(name.asString()))
// }
// }
// }
// val symbolProvider = originalFileFir.session.firSymbolProvider
//
// val allCallableNamesInPackage =
//// symbolProvider.getTopLevelCallableSymbols(originalFile.packageFqName, Name.identifier("bar"))
// symbolProvider.getAllCallableNamesInPackage(originalFileFir.packageFqName)
//
// for (it in allCallableNamesInPackage) {
// result.addElement(LookupElementBuilder.create(it.asString()))
// }
//
// val scope = ((nameExpression.getOrBuildFir() as? FirQualifiedAccessExpression)?.explicitReceiver as? FirFunctionCall)?.typeRef?.let {
// (symbolProvider.getSymbolByTypeRef<AbstractFirBasedSymbol<*>>(it) as? FirClassSymbol<*>)?.classId?.let {
// symbolProvider.getAllCallableNamesInClass(it)
// }
// }
//
//// val type = (((nameExpression.getOrBuildFir() as FirQualifiedAccessExpression).explicitReceiver as FirFunctionCall).typeRef as FirResolvedTypeRef).type as ConeClassLikeType
//// originalFileFir.session.declaredMemberScopeProvider.declaredMemberScope(type.lookupTag.toSymbol(originalFileFir.session).fir as FirRegularClass)
//
return
}
interface ScopeWrapper {
fun availableNames(): Set<Name>
}
fun getScopes(element: FirQualifiedAccessExpression, session: FirSession): Sequence<ScopeWrapper> = sequence {
val receiver = element.explicitReceiver
if (receiver != null) {
val explicitReceiverType = receiver?.typeRef?.coneTypeUnsafe<ConeKotlinType>()
val firScope = receiver.typeRef.coneTypeUnsafe<ConeKotlinType>().scope(session, ScopeSession())
// (symbolProvider.getSymbolByTypeRef<FirClassSymbol<*>>(receiver.typeRef))?.buildUseSiteMemberScope(firFunction.session, ScopeSession()) ?: return
val scopes: Sequence<FirScope> = sequence {
val explicitReceiverScope = explicitReceiverType?.scope(completionContext.session, ScopeSession())
if (explicitReceiverScope != null) {
yield(explicitReceiverScope)
}
if (firScope != null) {
yield(object : ScopeWrapper {
override fun availableNames(): Set<Name> {
return firScope.getCallableNames()
yieldAll(towerDataContext.localScopes)
val implicitReceiversScopes = towerDataContext.nonLocalTowerDataElements.mapNotNull { it.implicitReceiver?.implicitScope }
yieldAll(implicitReceiversScopes)
val nonLocalScopes = towerDataContext.nonLocalTowerDataElements.mapNotNull { it.scope }
yieldAll(nonLocalScopes)
}
for (scope in scopes) {
for (name in scope.getCallableNames()) {
if (name.isConstructor) continue
fun processor(symbol: FirCallableSymbol<*>) {
val expectedReceiverType = symbol.fir.receiverTypeRef?.coneTypeUnsafe<ConeKotlinType>()
if (expectedReceiverType != null) {
val receiverTypes = if (explicitReceiverType != null) {
listOf(explicitReceiverType)
} else {
towerDataContext.nonLocalTowerDataElements.mapNotNull { it.implicitReceiver?.type }
}
val expectedReceiverTypeIsPresent = receiverTypes.any {
AbstractTypeChecker.isSubtypeOf(completionContext.session.typeContext, it, expectedReceiverType)
}
if (expectedReceiverTypeIsPresent) {
result.addElement(LookupElementBuilder.create(name.asString()))
}
} else if (explicitReceiverType == null || symbol.callableId.classId == explicitReceiverType.classId) {
result.addElement(LookupElementBuilder.create(name.asString()))
}
})
}
scope.processFunctionsByName(name, ::processor)
scope.processPropertiesByName(name, ::processor)
}
}
}
private val Name.isConstructor get() = this == Name.special("<init>")
}