From d2011f7147305860337ae84c02d7d9a798a67c71 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 30 Jun 2017 21:11:42 +0300 Subject: [PATCH] PY-18816 Extract common algorithm for resolving symbols by their qualified names Use it both to resolve base and metaclasses by their qualified names saved in stubs in PyClassImpl and to process type annotations extracted from stubs in PyTypingTypeProvider. --- .../typing/PyTypingTypeProvider.java | 49 ++------------ .../python/psi/impl/PyClassImpl.java | 66 +------------------ .../python/psi/resolve/PyResolveUtil.java | 54 +++++++++++++++ 3 files changed, 59 insertions(+), 110 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index be03327b2783..c7b0bd06bf65 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -40,9 +40,8 @@ import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.impl.stubs.PyClassElementType; import com.jetbrains.python.psi.impl.stubs.PyTypingAliasStubType; -import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.PyResolveImportUtil; -import com.jetbrains.python.psi.resolve.RatedResolveResult; +import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.types.*; import one.util.streamex.StreamEx; @@ -418,7 +417,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } } for (QualifiedName qName : allBaseClassesQNames) { - final List classes = resolveQualifiedNameInFile(qName, (PyFile)pyClass.getContainingFile(), context); + final List classes = PyResolveUtil.resolveQualifiedNameInFile(qName, (PyFile)pyClass.getContainingFile(), context); // Better way to handle results of the multiresove final PyClass firstFound = ContainerUtil.findInstance(classes, PyClass.class); if (firstFound != null) { @@ -865,51 +864,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { final PyFile pyFile = as(FileContextUtil.getContextFile(expression), PyFile.class); if (pyFile != null && qualifiedName != null) { - return resolveQualifiedNameInFile(qualifiedName, pyFile, context); + return PyResolveUtil.resolveQualifiedNameInFile(qualifiedName, pyFile, context); } return Collections.singletonList(expression); } - @NotNull - private static List resolveQualifiedNameInFile(@NotNull QualifiedName qualifiedName, - @NotNull PyFile pyFile, - @NotNull TypeEvalContext context) { - if (qualifiedName.getComponentCount() > 0) { - List results = new ArrayList<>(); - final String first = qualifiedName.getFirstComponent(); - //noinspection ConstantConditions - results.addAll(pyFile.multiResolveName(first, false)); - if (results.isEmpty()) { - final PsiElement builtinSymbol = PyBuiltinCache.getInstance(pyFile).getByName(first); - if (builtinSymbol != null) { - results.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, builtinSymbol)); - } - } - - final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context); - - for (String name : qualifiedName.removeHead(1).getComponents()) { - final List children = new ArrayList<>(); - for (RatedResolveResult result : results) { - final PsiElement element = result.getElement(); - if (element instanceof PyTypedElement) { - final PyType type = context.getType((PyTypedElement)element); - if (type != null) { - final List resolved = - type.resolveMember(name, null, AccessDirection.READ, resolveContext); - if (resolved != null) { - children.addAll(resolved); - } - } - } - } - results = children; - } - return PyUtil.filterTopPriorityResults(results.toArray(RatedResolveResult.EMPTY_ARRAY)); - } - return Collections.emptyList(); - } - /** * Return the qualified name containing all names in the given (possibly qualified) reference expression. * If any of the qualifiers is not a reference expression, returns null. @@ -917,7 +876,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { * For instance, for the expression "foo.bar.baz" it returns the qualified name "foo.bar.baz", * but for "foo[0].bar.baz" it will return null. *

- * If you need to take into account such implicit "magical" names, use {@link com.jetbrains.python.psi.impl.PyPsiUtils#asQualifiedName(PyExpression)} + * If you need to take into account such implicit "magical" names, use {@link PyPsiUtils#asQualifiedName(PyExpression)} * or {@link PyQualifiedExpression#asQualifiedName()}. * @param expression */ diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index b8115fdd5782..3e1ebeba1eff 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -44,7 +44,6 @@ import com.jetbrains.python.psi.impl.stubs.PyClassElementType; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; -import com.jetbrains.python.psi.resolve.RatedResolveResult; import com.jetbrains.python.psi.stubs.PropertyStubStorage; import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.stubs.PyFunctionStub; @@ -1708,73 +1707,10 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla return results; } - @Nullable - private static PsiElement getElementQNamed(@NotNull PyFile file, @NotNull QualifiedName qualifiedName, @NotNull TypeEvalContext context) { - if (qualifiedName.getComponentCount() == 0) { - return null; - } - final String first = qualifiedName.getFirstComponent(); - if (first == null) { - return null; - } - final QualifiedName rest = qualifiedName.removeHead(1); - final PsiElement firstElement = file.multiResolveName(first, false) - .stream() - .map(RatedResolveResult::getElement) - .findFirst() - .orElse(PyBuiltinCache.getInstance(file).getByName(first)); - if (rest.getComponentCount() == 0) { - return firstElement; - } - final PyTypedElement typedElement = as(firstElement, PyTypedElement.class); - if (typedElement == null) { - return null; - } - final String name = rest.getLastComponent(); - final QualifiedName containingQName = rest.removeLastComponent(); - PyType currentType = context.getType(typedElement); - if (currentType == null) { - return null; - } - for (String component : containingQName.getComponents()) { - currentType = getMemberType(currentType, component, context); - if (currentType == null) { - return null; - } - } - if (name != null) { - return resolveTypeMember(currentType, name, context); - } - return null; - } - - @Nullable - private static PyType getMemberType(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) { - final PyType result; - PsiElement element = resolveTypeMember(type, name, context); - if (element instanceof PyTypedElement) { - result = context.getType((PyTypedElement)element); - } - else { - return null; - } - if (result instanceof PyClassLikeType) { - return ((PyClassLikeType)result).toInstance(); - } - return result; - } - - @Nullable - private static PsiElement resolveTypeMember(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) { - final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); - final List results = type.resolveMember(name, null, AccessDirection.READ, resolveContext); - return (results != null && !results.isEmpty()) ? results.get(0).getElement() : null; - } - @Nullable private static PyClassLikeType classTypeFromQName(@NotNull QualifiedName qualifiedName, @NotNull PyFile containingFile, @NotNull TypeEvalContext context) { - final PsiElement element = getElementQNamed(containingFile, qualifiedName, context); + final PsiElement element = ContainerUtil.getFirstItem(PyResolveUtil.resolveQualifiedNameInFile(qualifiedName, containingFile, context)); if (element instanceof PyTypedElement) { final PyType type = context.getType((PyTypedElement)element); if (type instanceof PyClassLikeType) { diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index 73c0a31e9b45..6d608ba3a0b1 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -19,17 +19,25 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.psi.types.PyClassLikeType; +import com.jetbrains.python.psi.types.TypeEvalContext; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; +import java.util.List; + +import static com.jetbrains.python.psi.PyUtil.as; /** * @author vlan @@ -125,4 +133,50 @@ public class PyResolveUtil { return processor.getElements(); } + + /** + * Resolve a symbol by its qualified name, starting from the specified file and then following the chain of type members. + * This type of resolve is stub-safe, i.e. it's not supposed to cause any un-stubbing of external files unless it explicitly + * allowed by the given type evaluation context. + * + * @param qualifiedName name of a symbol to resolve + * @param file module which serves as a starting point for resolve + * @return all possible candidates that can be found by the given qualified name + */ + @NotNull + public static List resolveQualifiedNameInFile(@NotNull QualifiedName qualifiedName, + @NotNull PyFile file, + @NotNull TypeEvalContext context) { + final String firstName = qualifiedName.getFirstComponent(); + if (firstName == null) { + return Collections.emptyList(); + } + final List unqualifiedResults = file.multiResolveName(firstName, false); + final StreamEx initialResults; + if (unqualifiedResults.isEmpty()) { + final PsiElement builtin = PyBuiltinCache.getInstance(file).getByName(firstName); + if (builtin == null) { + return Collections.emptyList(); + } + initialResults = StreamEx.of(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, builtin)); + } + else { + initialResults = StreamEx.of(unqualifiedResults); + } + final List remainingNames = qualifiedName.removeHead(1).getComponents(); + final StreamEx result = StreamEx.of(remainingNames).reduce(initialResults, (prev, name) -> + prev + .map(RatedResolveResult::getElement) + .map(elem -> as(elem, PyTypedElement.class)) + .nonNull() + .map(context::getType) + .nonNull() + .map(type -> type instanceof PyClassLikeType ? ((PyClassLikeType)type).toInstance() : type) + .flatMap(elemType -> { + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + final List results = elemType.resolveMember(name, null, AccessDirection.READ, resolveContext); + return results != null ? StreamEx.of(results) : StreamEx.empty(); + }), (oldResults, newResults) -> newResults); + return PyUtil.filterTopPriorityResults(result.toArray(RatedResolveResult[]::new)); + } }