diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinTypeDeclarationProvider.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinTypeDeclarationProvider.kt index 051da0e6dd8..fcc41f786f1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinTypeDeclarationProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinTypeDeclarationProvider.kt @@ -18,24 +18,48 @@ package org.jetbrains.kotlin.idea.codeInsight import com.intellij.codeInsight.navigation.actions.TypeDeclarationProvider import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.KotlinType class KotlinTypeDeclarationProvider : TypeDeclarationProvider { override fun getSymbolTypeDeclarations(symbol: PsiElement): Array? { - if (symbol !is KtElement || symbol.getContainingFile() !is KtFile) return emptyArray() + if (symbol.containingFile !is KtFile) return emptyArray() - val bindingContext = symbol.analyze() - val callableDescriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, symbol) - if (callableDescriptor !is CallableDescriptor) return emptyArray() + if (symbol is PsiWhiteSpace) { + // Navigate to type of first parameter in lambda, works with the help of KotlinTargetElementEvaluator for the 'it' case + val lBraceElement = symbol.containingFile.findElementAt(maxOf(symbol.textOffset - 1, 0)) + if (lBraceElement?.text == "{") { + val functionLiteral = lBraceElement.parent as? KtFunctionLiteral + if (functionLiteral != null) { + return functionLiteral.getTypeDeclarationFromCallable { descriptor -> descriptor.valueParameters.firstOrNull()?.type } + } + } + } - val type = callableDescriptor.returnType ?: return emptyArray() + if (symbol is KtFunctionLiteral) { + // Navigate to receiver type of extension lambda + return symbol.getTypeDeclarationFromCallable { descriptor -> descriptor.extensionReceiverParameter?.type } + } - val classifierDescriptor = type.constructor.declarationDescriptor ?: return emptyArray() - return DescriptorToSourceUtilsIde.getAllDeclarations(symbol.project, classifierDescriptor).toTypedArray() + if (symbol is KtTypeReference) { + val declaration = symbol.parent + if (declaration is KtCallableDeclaration && declaration.receiverTypeReference == symbol) { + // Navigate to function receiver type, works with the help of KotlinTargetElementEvaluator for the 'this' in extension declaration + return declaration.getTypeDeclarationFromCallable { descriptor -> descriptor.extensionReceiverParameter?.type } + } + } + + if (symbol !is KtDeclaration) return emptyArray() + return symbol.getTypeDeclarationFromCallable { descriptor -> descriptor.returnType } } -} + + private fun KtDeclaration.getTypeDeclarationFromCallable(typeFromDescriptor: (CallableDescriptor) -> KotlinType?): Array? { + val callableDescriptor = resolveToDescriptorIfAny() as? CallableDescriptor ?: return emptyArray() + val type = typeFromDescriptor(callableDescriptor) ?: return emptyArray() + val classifierDescriptor = type.constructor.declarationDescriptor ?: return emptyArray() + return DescriptorToSourceUtilsIde.getAllDeclarations(project, classifierDescriptor).toTypedArray() + } +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationName.test b/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationName.test new file mode 100644 index 00000000000..1d7ec093676 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationName.test @@ -0,0 +1,12 @@ +// FILE: before.kt +interface Foo1 +interface Bar1 + +fun Foo1.test(): Bar1 = null!! + + +// FILE: after.kt +interface Foo1 +interface Bar1 + +fun Foo1.test(): Bar1 = null!! \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationReturn.test b/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationReturn.test new file mode 100644 index 00000000000..43401550c3b --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationReturn.test @@ -0,0 +1,12 @@ +// FILE: before.kt +interface Foo1 +interface Bar1 + +fun Foo1.test(): Bar1 = null!! + + +// FILE: after.kt +interface Foo1 +interface Bar1 + +fun Foo1.test(): Bar1 = null!! \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclrationExtension.test b/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclrationExtension.test new file mode 100644 index 00000000000..a4388fe6382 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclrationExtension.test @@ -0,0 +1,12 @@ +// FILE: before.kt +interface Foo1 +interface Bar1 + +fun Foo1.test(): Bar1 = null!! + + +// FILE: after.kt +interface Foo1 +interface Bar1 + +fun Foo1.test(): Bar1 = null!! \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/itExtensionLambda.test b/idea/testData/navigation/gotoTypeDeclaration/itExtensionLambda.test new file mode 100644 index 00000000000..6060767e1b6 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/itExtensionLambda.test @@ -0,0 +1,23 @@ +// FILE: before.kt +interface Foo +interface Bar + +fun foo(a: Foo.(Bar) -> Unit) {} + +fun bar() { + foo { + it + } +} + +// FILE: after.kt +interface Foo +interface Bar + +fun foo(a: Foo.(Bar) -> Unit) {} + +fun bar() { + foo { + it + } +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/itParameterInLambda.test b/idea/testData/navigation/gotoTypeDeclaration/itParameterInLambda.test new file mode 100644 index 00000000000..8b715ef70ba --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/itParameterInLambda.test @@ -0,0 +1,23 @@ +// FILE: before.kt +class Foo + +fun some(a: T, f: (T) -> Unit) {} +fun foo(a: Any) {} + +fun baz() { + some(Foo()) { + foo(it) + } +} + +// FILE: after.kt +class Foo + +fun some(a: T, f: (T) -> Unit) {} +fun foo(a: Any) {} + +fun baz() { + some(Foo()) { + foo(it) + } +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/noParametersLambda.test b/idea/testData/navigation/gotoTypeDeclaration/noParametersLambda.test new file mode 100644 index 00000000000..2657fc5c422 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/noParametersLambda.test @@ -0,0 +1,9 @@ +// FILE: before.kt +class Foo + +val f: () -> Foo = { } + +// FILE: after.kt +class Foo + +val f: () -> Foo = { } \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunction.test b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunction.test new file mode 100644 index 00000000000..0e1230c1f60 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunction.test @@ -0,0 +1,18 @@ +// FILE: before.kt +interface Foo + +fun foo(a: Any) {} + +fun Foo.bar() { + foo(this) +} + + +// FILE: after.kt +interface Foo + +fun foo(a: Any) {} + +fun Foo.bar() { + foo(this) +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithAnnotationOnReceiver.test b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithAnnotationOnReceiver.test new file mode 100644 index 00000000000..f6b108097eb --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithAnnotationOnReceiver.test @@ -0,0 +1,15 @@ +// FILE: before.kt +interface Foo + +@Target(AnnotationTarget.TYPE) +annotation class A + +fun @A Foo.bar(): Any = this + +// FILE: after.kt +interface Foo + +@Target(AnnotationTarget.TYPE) +annotation class A + +fun @A Foo.bar(): Any = this \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithGenericReceiver.test b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithGenericReceiver.test new file mode 100644 index 00000000000..41e87e059b4 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithGenericReceiver.test @@ -0,0 +1,9 @@ +// FILE: before.kt +interface Foo + +fun Foo.bar(): Any = this + +// FILE: after.kt +interface Foo + +fun Foo.bar(): Any = this \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/thisExtensionLambda.test b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionLambda.test new file mode 100644 index 00000000000..8151f699c2c --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/thisExtensionLambda.test @@ -0,0 +1,21 @@ +// FILE: before.kt +interface Foo + +fun foo(a: Foo.() -> Unit) {} + +fun bar() { + foo { + this + } +} + +// FILE: after.kt +interface Foo + +fun foo(a: Foo.() -> Unit) {} + +fun bar() { + foo { + this + } +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTypeDeclaration/thisInExtensionPropertyAccessor.test b/idea/testData/navigation/gotoTypeDeclaration/thisInExtensionPropertyAccessor.test new file mode 100644 index 00000000000..b8f202be525 --- /dev/null +++ b/idea/testData/navigation/gotoTypeDeclaration/thisInExtensionPropertyAccessor.test @@ -0,0 +1,19 @@ +// FILE: before.kt +interface Foo +interface Bar + +val Foo.test: Bar + get() { + this + return null!! + } + +// FILE: after.kt +interface Foo +interface Bar + +val Foo.test: Bar + get() { + this + return null!! + } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/navigation/GotoTypeDeclarationTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/navigation/GotoTypeDeclarationTestGenerated.java index c7a21a147f3..17b1b6552f6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/navigation/GotoTypeDeclarationTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/navigation/GotoTypeDeclarationTestGenerated.java @@ -48,12 +48,78 @@ public class GotoTypeDeclarationTestGenerated extends AbstractGotoTypeDeclaratio doTest(fileName); } + @TestMetadata("fromFunctionDeclarationName.test") + public void testFromFunctionDeclarationName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationName.test"); + doTest(fileName); + } + + @TestMetadata("fromFunctionDeclarationReturn.test") + public void testFromFunctionDeclarationReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclarationReturn.test"); + doTest(fileName); + } + + @TestMetadata("fromFunctionDeclrationExtension.test") + public void testFromFunctionDeclrationExtension() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/fromFunctionDeclrationExtension.test"); + doTest(fileName); + } + @TestMetadata("functionCall.test") public void testFunctionCall() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/functionCall.test"); doTest(fileName); } + @TestMetadata("itExtensionLambda.test") + public void testItExtensionLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/itExtensionLambda.test"); + doTest(fileName); + } + + @TestMetadata("itParameterInLambda.test") + public void testItParameterInLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/itParameterInLambda.test"); + doTest(fileName); + } + + @TestMetadata("noParametersLambda.test") + public void testNoParametersLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/noParametersLambda.test"); + doTest(fileName); + } + + @TestMetadata("thisExtensionFunction.test") + public void testThisExtensionFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunction.test"); + doTest(fileName); + } + + @TestMetadata("thisExtensionFunctionWithAnnotationOnReceiver.test") + public void testThisExtensionFunctionWithAnnotationOnReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithAnnotationOnReceiver.test"); + doTest(fileName); + } + + @TestMetadata("thisExtensionFunctionWithGenericReceiver.test") + public void testThisExtensionFunctionWithGenericReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/thisExtensionFunctionWithGenericReceiver.test"); + doTest(fileName); + } + + @TestMetadata("thisExtensionLambda.test") + public void testThisExtensionLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/thisExtensionLambda.test"); + doTest(fileName); + } + + @TestMetadata("thisInExtensionPropertyAccessor.test") + public void testThisInExtensionPropertyAccessor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/thisInExtensionPropertyAccessor.test"); + doTest(fileName); + } + @TestMetadata("variableType.test") public void testVariableType() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTypeDeclaration/variableType.test");