[python] PY-82265 edge cases for GTD

(cherry picked from commit 6de5053ef51534f6efe8a1ca4cf428b8d8c44cfa)

IJ-MR-166989

GitOrigin-RevId: 32b61d838cb740f75b4d16842a11bda770ec87fa
This commit is contained in:
Morgan Bartholomew
2025-06-25 17:33:35 +02:00
committed by intellij-monorepo-bot
parent 8578aba478
commit e812c7d44b
6 changed files with 41 additions and 27 deletions

View File

@@ -21,7 +21,6 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.resolve.FileContextUtil
import com.jetbrains.python.PyUserInitiatedResolvableReference
import com.jetbrains.python.psi.PyElement
import com.jetbrains.python.psi.PyImportElement
import com.jetbrains.python.psi.PyReferenceOwner
import com.jetbrains.python.psi.resolve.PyResolveContext
import com.jetbrains.python.psi.resolve.PyResolveUtil
@@ -52,28 +51,18 @@ class PyGotoDeclarationHandler : GotoDeclarationHandlerBase() {
val parent = sourceElement.parent
val referenceOwner = sourceElement as? PyReferenceOwner ?: parent as? PyReferenceOwner
if (referenceOwner != null) {
val resolved = PyResolveUtil.multiResolveDeclaration(referenceOwner.getReference(context), context)
if (resolved != null) {
val stubResults =
resolved
.filter {
PyiUtil.isInsideStub(it) && !PyiUtil.isInsideStub(FileContextUtil.getContextFile(sourceElement)!!)
}
.map {
val results = PyResolveUtil.multiResolveDeclaration(referenceOwner.getReference(context), context)
.map {
if (PyiUtil.isInsideStub(it) && !PyiUtil.isInsideStub(FileContextUtil.getContextFile(sourceElement)!!)) {
PyiUtil.getOriginalElement(it as PyElement) ?: it
}
if (stubResults.isNotEmpty()) {
return stubResults.toTypedArray()
}
val results = resolved
.filter { it !== referenceOwner && it !is PyImportElement }
.groupBy { it.containingFile }
// include all the definitions from the current file, otherwise just the top result
.flatMap { if (it.key == sourceElement.containingFile) it.value else listOf(it.value.first()) }
if (results.isNotEmpty()) {
return results.toTypedArray()
}
else it
}
.filter { it !== referenceOwner }
.groupBy { it.containingFile }
.flatMap { if (it.key == sourceElement.containingFile) it.value else listOf(it.value.first()) }
if (results.isNotEmpty()) {
return results.toTypedArray()
}
}

View File

@@ -521,7 +521,7 @@ public final class PyResolveUtil {
return rate;
}
public static @Nullable List<PsiElement> multiResolveDeclaration(@NotNull PsiReference reference, @NotNull PyResolveContext resolveContext) {
public static @NotNull List<@NotNull PsiElement> multiResolveDeclaration(@NotNull PsiReference reference, @NotNull PyResolveContext resolveContext) {
final PsiElement element = reference.getElement();
final var context = resolveContext.getTypeEvalContext();
@@ -534,7 +534,7 @@ public final class PyResolveUtil {
final var constructor = ContainerUtil.find(
PyUtil.filterTopPriorityElements(PyCallExpressionHelper.resolveImplicitlyInvokedMethods(type, call, resolveContext)),
it -> it instanceof PyPossibleClassMember && ((PyPossibleClassMember)it).getContainingClass() == cls
it -> it instanceof PyPossibleClassMember possibleClassMember && possibleClassMember.getContainingClass() == cls
);
if (constructor != null) {
@@ -544,16 +544,16 @@ public final class PyResolveUtil {
}
if (reference instanceof PsiPolyVariantReference multiReference) {
return Stream.of(multiReference.multiResolve(false)).map(result -> result.getElement()).toList();
return PyUtil.multiResolveTopPriority(multiReference);
}
final var result = reference.resolve();
if (result == null) return null;
if (result == null) return List.of();
return List.of(result);
}
public static @Nullable PsiElement resolveDeclaration(@NotNull PsiReference reference, @NotNull PyResolveContext resolveContext) {
final var result = multiResolveDeclaration(reference, resolveContext);
if (result == null || result.isEmpty()) return null;
if (result.isEmpty()) return null;
return result.get(0);
}

View File

@@ -0,0 +1,2 @@
class A:
pass

View File

@@ -0,0 +1 @@
class A: ...

View File

@@ -228,7 +228,7 @@ class PyNavigationTest : PyTestCase() {
myFixture.copyDirectoryToProject(getTestName(true), "")
myFixture.configureByFile("test.py")
val target = PyGotoDeclarationHandler().getGotoDeclarationTarget(elementAtCaret, myFixture.editor)
TestCase.assertNotNull(target)
assertNotNull(target)
assertInstanceOf(target, PyFunction::class.java)
checkPyNotPyi(target?.containingFile)
}
@@ -402,6 +402,27 @@ class PyNavigationTest : PyTestCase() {
}
}
fun `test multi with pyi`() {
myFixture.copyDirectoryToProject("test_multi_with_pyi", "")
val (stubbed, local) = checkMulti(
"""
if bool():
from stubbed import A
else:
class A:
pass
<caret>A
""".trimIndent(),
2
)
checkPyNotPyi(stubbed.containingFile)
assertEquals("stubbed.py", stubbed.containingFile.name)
checkPyNotPyi(local.containingFile)
assertEquals("test.py", local.containingFile.name)
}
private fun doTestGotoDeclarationNavigatesToPyNotPyi() {
myFixture.copyDirectoryToProject(getTestName(true), "")
myFixture.configureByFile("test.py")

View File

@@ -58,6 +58,7 @@ import com.jetbrains.python.psi.search.PySearchUtilBase;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.sdk.PythonSdkUtil;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;