mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
PY-74257 Unresolved method reference is not highlighted for a derived class when the superclass is three levels package deep
- improve resolve algorithm wrt. some corner cases related to packages GitOrigin-RevId: 67268a2d8f8a4614ea4bbdbbdaa613c0fa165ef4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
76479bf81d
commit
718d7187f3
@@ -61,10 +61,10 @@ public final class ScopeUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scope owner for the element.
|
||||
*
|
||||
* Return the scope owner for the element. This also applies for elements of instance {@code AstScopeOwner}.
|
||||
* <br/>
|
||||
* Scope owner is not always the first ScopeOwner parent of the element. Some elements are resolved in outer scopes.
|
||||
*
|
||||
* <br/>
|
||||
* This method does not access AST if underlying PSI is stub based.
|
||||
*/
|
||||
public static @Nullable ScopeOwner getScopeOwner(final @Nullable PsiElement element) {
|
||||
|
||||
@@ -35,10 +35,7 @@ import com.jetbrains.python.psi.impl.*;
|
||||
import com.jetbrains.python.psi.search.PySearchUtilBase;
|
||||
import com.jetbrains.python.psi.stubs.PyClassAttributesIndex;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionNameIndex;
|
||||
import com.jetbrains.python.psi.types.PyClassLikeType;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import com.jetbrains.python.pyi.PyiFile;
|
||||
import com.jetbrains.python.pyi.PyiUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -285,19 +282,24 @@ public final class PyResolveUtil {
|
||||
|
||||
final List<String> remainingNames = qualifiedName.removeHead(1).getComponents();
|
||||
final List<RatedResolveResult> result = StreamEx.of(remainingNames).foldLeft(StreamEx.of(unqualifiedResults), (prev, name) ->
|
||||
prev
|
||||
.map(RatedResolveResult::getElement)
|
||||
.select(PyTypedElement.class)
|
||||
.map(context::getType)
|
||||
.nonNull()
|
||||
.flatMap(type -> {
|
||||
// An instance type has access to instance attributes defined in __init__, a class type does not.
|
||||
final PyType instanceType = type instanceof PyClassLikeType ? ((PyClassLikeType)type).toInstance() : type;
|
||||
final List<? extends RatedResolveResult> results = instanceType.resolveMember(name, null, AccessDirection.READ, resolveContext);
|
||||
return results != null ? StreamEx.of(results) : StreamEx.<RatedResolveResult>empty();
|
||||
}))
|
||||
.toList();
|
||||
prev
|
||||
.map(RatedResolveResult::getElement)
|
||||
.select(PyTypedElement.class)
|
||||
.map(context::getType)
|
||||
.nonNull()
|
||||
.flatMap(type -> {
|
||||
assert type != null; // see filter nonNull()
|
||||
// An instance type has access to instance attributes defined in __init__, a class type does not.
|
||||
final PyType instanceType = type instanceof PyClassLikeType ? ((PyClassLikeType)type).toInstance() : type;
|
||||
final List<? extends RatedResolveResult> results = instanceType instanceof PyModuleType moduleType
|
||||
? moduleType.resolveModuleMember(name, scopeOwner, AccessDirection.READ,
|
||||
resolveContext)
|
||||
: instanceType.resolveMember(name, null, AccessDirection.READ,
|
||||
resolveContext);
|
||||
|
||||
return results != null ? StreamEx.of(results) : StreamEx.<RatedResolveResult>empty();
|
||||
}))
|
||||
.toList();
|
||||
return PyUtil.filterTopPriorityElements(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,10 @@ import com.jetbrains.python.codeInsight.mlcompletion.PyCompletionMlElementInfo;
|
||||
import com.jetbrains.python.codeInsight.mlcompletion.PyCompletionMlElementKind;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyImportedModule;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.impl.ResolveResultList;
|
||||
import com.jetbrains.python.psi.resolve.*;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -61,19 +63,28 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
@Nullable PyExpression location,
|
||||
@NotNull AccessDirection direction,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
return resolveModuleMember(name, location, direction, resolveContext);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public @Nullable List<? extends RatedResolveResult> resolveModuleMember(final @NotNull String name,
|
||||
@Nullable PsiElement scopeOwner,
|
||||
@NotNull AccessDirection direction,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
if (MODULE_MEMBERS.contains(name)) {
|
||||
var type = getModuleClassType();
|
||||
if (type != null) {
|
||||
return type.resolveMember(name, location, direction, resolveContext);
|
||||
PyExpression pyExpression = scopeOwner instanceof PyExpression expression ? expression : null;
|
||||
return type.resolveMember(name, pyExpression, direction, resolveContext);
|
||||
}
|
||||
}
|
||||
return resolveMemberInPackageOrModule(null, myModule, name, location, resolveContext);
|
||||
return resolveMemberInPackageOrModule(null, myModule, name, scopeOwner, resolveContext);
|
||||
}
|
||||
|
||||
public static @Nullable List<? extends RatedResolveResult> resolveMemberInPackageOrModule(@Nullable PyImportedModule importedModule,
|
||||
@NotNull PsiFileSystemItem anchor,
|
||||
@NotNull String name,
|
||||
@Nullable PyExpression location,
|
||||
@Nullable PsiElement location,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
|
||||
|
||||
@@ -96,7 +107,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
processImplicitPackageMembers(anchor, location, importedModule, n -> name.endsWith(n), results -> {
|
||||
implicitMembers.addAll(convertDirsToInit(results));
|
||||
return implicitMembers.isEmpty();
|
||||
});
|
||||
}, resolveContext);
|
||||
if (!implicitMembers.isEmpty()) {
|
||||
return implicitMembers;
|
||||
}
|
||||
@@ -115,30 +126,37 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
@Nullable PsiElement location,
|
||||
@Nullable PyImportedModule importedModule,
|
||||
@NotNull Predicate<String> filter,
|
||||
@NotNull Processor<List<? extends RatedResolveResult>> resultProcessor) {
|
||||
@NotNull Processor<List<? extends RatedResolveResult>> resultProcessor,
|
||||
@Nullable PyResolveContext resolveContext) {
|
||||
final PsiElement realLocation = location == null ? null : PyPsiUtils.getRealContext(location);
|
||||
final List<PyImportElement> importElements = new ArrayList<>();
|
||||
final PyFile module = PyUtil.as(PyUtil.turnDirIntoInit(anchor), PyFile.class);
|
||||
if (anchor.getVirtualFile() == null) {
|
||||
return;
|
||||
}
|
||||
final PsiElement footHold = location != null ? location.getContainingFile() : module;
|
||||
final PsiElement footHold = realLocation != null ? realLocation.getContainingFile() : (module != null ? module : anchor);
|
||||
if (footHold == null) {
|
||||
return;
|
||||
}
|
||||
final PyImportElement origImportElement = importedModule != null ? importedModule.getImportElement() : null;
|
||||
if (importedModule != null && (location == null || !inSameFile(location, importedModule))) {
|
||||
if (importedModule != null && (realLocation == null || !inSameFile(realLocation, importedModule))) {
|
||||
if (origImportElement != null) {
|
||||
importElements.add(origImportElement);
|
||||
}
|
||||
}
|
||||
else if (location != null) {
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(location);
|
||||
if (owner != null) {
|
||||
else if (realLocation != null) {
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(realLocation);
|
||||
final boolean crawlScope =
|
||||
owner != null && (resolveContext == null || resolveContext.getTypeEvalContext().maySwitchToAST(realLocation));
|
||||
if (crawlScope) {
|
||||
importElements.addAll(getVisibleImports(owner));
|
||||
}
|
||||
else if (realLocation instanceof PyFile pyFile) {
|
||||
importElements.addAll(getTopImports(pyFile));
|
||||
}
|
||||
|
||||
if (module != null) {
|
||||
if (!inSameFile(location, module)) {
|
||||
if (!inSameFile(realLocation, module)) {
|
||||
importElements.addAll(module.getImportTargets());
|
||||
}
|
||||
final List<PyFromImportStatement> imports = module.getFromImports();
|
||||
@@ -198,7 +216,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
if (location.getContainingFile().getVirtualFile() == null) {
|
||||
return;
|
||||
}
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(location);
|
||||
final ScopeOwner owner = location instanceof ScopeOwner ? (ScopeOwner)location : ScopeUtil.getScopeOwner(location);
|
||||
if (owner == null) {
|
||||
return;
|
||||
}
|
||||
@@ -215,8 +233,8 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
if (directChild != null && filter.test(directChild)) {
|
||||
final QualifiedName mainPackage = QualifiedName.fromComponents(locationQName.getFirstComponent());
|
||||
final PyImportElement packageImportElement =
|
||||
visibleImports.stream().filter(el -> getImportedQNames(el).stream().anyMatch(qName -> qName.matchesPrefix(mainPackage)))
|
||||
.findFirst().orElse(null);
|
||||
ContainerUtil.find(visibleImports,
|
||||
el -> ContainerUtil.exists(getImportedQNames(el), qName -> qName.matchesPrefix(mainPackage)));
|
||||
|
||||
if (packageImportElement != null) {
|
||||
final List<RatedResolveResult> results =
|
||||
@@ -336,6 +354,14 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
return importedQNames;
|
||||
}
|
||||
|
||||
private static @NotNull List<PyImportElement> getTopImports(@NotNull PyFile file) {
|
||||
List<PyImportElement> importElements = new ArrayList<>(file.getImportTargets());
|
||||
for (PyFromImportStatement fromImportStatement : file.getFromImports()) {
|
||||
importElements.addAll(Arrays.asList(fromImportStatement.getImportElements()));
|
||||
}
|
||||
return importElements;
|
||||
}
|
||||
|
||||
private static @NotNull List<PyImportElement> getVisibleImports(@NotNull ScopeOwner owner) {
|
||||
final List<PyImportElement> visibleImports = new ArrayList<>();
|
||||
PyResolveUtil.scopeCrawlUp(new PsiScopeProcessor() {
|
||||
@@ -487,7 +513,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
|
||||
|
||||
elements.addAll(ResolveResultList.getElements(results));
|
||||
return true;
|
||||
});
|
||||
}, null);
|
||||
return ContainerUtil.mapNotNull(elements,
|
||||
element -> {
|
||||
if (element instanceof PsiFileSystemItem) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import deriv
|
||||
|
||||
a1 = deriv.Simple()
|
||||
a1.<warning descr="Unresolved attribute reference 'xxx' for class 'Simple'">xxx</warning>()
|
||||
|
||||
b2 = deriv.DerivedBaseInit()
|
||||
b2.<warning descr="Unresolved attribute reference 'yyy' for class 'DerivedBaseInit'">yyy</warning>()
|
||||
@@ -0,0 +1,8 @@
|
||||
import project.base
|
||||
|
||||
|
||||
class Simple:
|
||||
pass
|
||||
|
||||
class DerivedBaseInit(project.base.BaseInit):
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
class BaseInit:
|
||||
pass
|
||||
@@ -0,0 +1,10 @@
|
||||
import project.deriv
|
||||
|
||||
a1 = project.deriv.Simple()
|
||||
a1.<warning descr="Unresolved attribute reference 'xxx' for class 'Simple'">xxx</warning>()
|
||||
|
||||
b2 = project.deriv.DerivedBase()
|
||||
b2.<warning descr="Unresolved attribute reference 'yyy' for class 'DerivedBase'">yyy</warning>()
|
||||
|
||||
c3 = project.deriv.DerivedSub()
|
||||
c3.<warning descr="Unresolved attribute reference 'zzz' for class 'DerivedSub'">zzz</warning>()
|
||||
@@ -0,0 +1,2 @@
|
||||
class BaseInit:
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
class BaseSub:
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
import project.base
|
||||
import project.base.sub
|
||||
|
||||
|
||||
class Simple:
|
||||
pass
|
||||
|
||||
|
||||
class DerivedBase(project.base.BaseInit):
|
||||
pass
|
||||
|
||||
|
||||
class DerivedSub(project.base.sub.BaseSub):
|
||||
pass
|
||||
@@ -0,0 +1,10 @@
|
||||
import project.deriv
|
||||
|
||||
a1 = project.deriv.Simple()
|
||||
a1.<warning descr="Unresolved attribute reference 'xxx' for class 'Simple'">xxx</warning>()
|
||||
|
||||
b2 = project.deriv.DerivedBase()
|
||||
b2.<warning descr="Unresolved attribute reference 'yyy' for class 'DerivedBase'">yyy</warning>()
|
||||
|
||||
c3 = project.deriv.DerivedSub()
|
||||
c3.<warning descr="Unresolved attribute reference 'zzz' for class 'DerivedSub'">zzz</warning>()
|
||||
@@ -0,0 +1,2 @@
|
||||
class BaseInit:
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
class BaseSub:
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
import project.base
|
||||
import project.base.sub
|
||||
|
||||
|
||||
class Simple:
|
||||
pass
|
||||
|
||||
|
||||
class DerivedBase(project.base.BaseInit):
|
||||
pass
|
||||
|
||||
|
||||
class DerivedSub(project.base.sub.BaseSub):
|
||||
pass
|
||||
@@ -0,0 +1,5 @@
|
||||
import pkg
|
||||
|
||||
|
||||
def f() -> "pkg.subpkg.mod.MyClass":
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
class MyClass:
|
||||
pass
|
||||
@@ -3966,6 +3966,15 @@ public class Py3TypeTest extends PyTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-74257
|
||||
public void testNotProperlyImportedQualifiedNameInTypeHint() {
|
||||
doMultiFileTest("Any", """
|
||||
from lib import f
|
||||
|
||||
expr = f()
|
||||
""");
|
||||
}
|
||||
|
||||
private void doTest(final String expectedType, final String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
|
||||
|
||||
@@ -891,6 +891,27 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
// PY-74257 simple example, depth two, no packages (__init__.py files)
|
||||
public void testImportFromNestedDirectory1() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
|
||||
doMultiFileTest("Main.py");
|
||||
});
|
||||
}
|
||||
|
||||
// PY-74257 depth three with packages, but implicit package 'project', i.e., no __init__.py in directory 'project'
|
||||
public void testImportFromNestedDirectory2() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
|
||||
doMultiFileTest("Main.py");
|
||||
});
|
||||
}
|
||||
|
||||
// PY-74257 depth three with packages, like before but with an explicit package 'project'
|
||||
public void testImportFromNestedDirectory3() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
|
||||
doMultiFileTest("Main.py");
|
||||
});
|
||||
}
|
||||
|
||||
// PY-78413
|
||||
public void testAsyncAwaitWarningOnImportedFun() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
|
||||
|
||||
Reference in New Issue
Block a user