mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Support for subclasses of namedtuple defined in the current file (PY-4345)
This commit is contained in:
@@ -2,31 +2,41 @@ package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.psi.impl.PyQualifiedName;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class PyClassRef {
|
||||
@Nullable
|
||||
private final PsiElement myElement;
|
||||
|
||||
@Nullable
|
||||
private final String myQName;
|
||||
@Nullable private final PsiElement myElement;
|
||||
@Nullable private final String myQName;
|
||||
@Nullable private final PyClassType myType;
|
||||
|
||||
public PyClassRef(@Nullable PsiElement element) {
|
||||
myElement = element;
|
||||
myQName = null;
|
||||
myType = null;
|
||||
}
|
||||
|
||||
public PyClassRef(@Nullable String qName) {
|
||||
myElement = null;
|
||||
myQName = qName;
|
||||
myType = null;
|
||||
}
|
||||
|
||||
public PyClassRef(@Nullable PyClassType type) {
|
||||
myElement = null;
|
||||
myQName = null;
|
||||
myType = type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyClass getPyClass() {
|
||||
return myElement instanceof PyClass ? (PyClass) myElement : null;
|
||||
if (myElement instanceof PyClass) {
|
||||
return (PyClass) myElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -34,26 +44,40 @@ public class PyClassRef {
|
||||
return myElement;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyClassType getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getClassName() {
|
||||
if (myElement instanceof PyClass) {
|
||||
return ((PyClass)myElement).getName();
|
||||
}
|
||||
if (myQName != null) {
|
||||
else if (myQName != null) {
|
||||
final PyQualifiedName qname = PyQualifiedName.fromDottedString(myQName);
|
||||
if (qname != null) {
|
||||
return qname.getLastComponent();
|
||||
}
|
||||
}
|
||||
else if (myType != null) {
|
||||
return myType.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getQualifiedName() {
|
||||
if (myQName != null) {
|
||||
if (myElement instanceof PyClass) {
|
||||
return ((PyClass)myElement).getQualifiedName();
|
||||
}
|
||||
else if (myQName != null) {
|
||||
return myQName;
|
||||
}
|
||||
return myElement instanceof PyClass ? ((PyClass)myElement).getQualifiedName() : null;
|
||||
else if (myType != null) {
|
||||
return myType.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -49,7 +49,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
|
||||
if (myFields.contains(name)) {
|
||||
return Collections.singletonList(new RatedResolveResult(1000, new PyElementImpl(myDeclaration.getNode())));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -60,6 +60,12 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) {
|
||||
if (PyNames.NAMEDTUPLE.equals(((PyFunction)referenceTarget).getName()) &&
|
||||
PyNames.COLLECTIONS_PY.equals(referenceTarget.getContainingFile().getName())) {
|
||||
return PyNamedTupleType.fromCall((PyCallExpression)anchor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -688,7 +688,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
|
||||
|
||||
private static boolean hasUnresolvedAncestors(PyClass cls) {
|
||||
for (PyClassRef classRef : cls.iterateAncestors()) {
|
||||
if (classRef.getPyClass() == null) {
|
||||
if (classRef.getPyClass() == null && classRef.getType() == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.jetbrains.python.psi.stubs.PropertyStubStorage;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.psi.stubs.PyTargetExpressionStub;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyClassTypeImpl;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
@@ -148,20 +149,21 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
final PyExpression[] superExpressions = getSuperClassExpressions();
|
||||
List<PsiElement> superClasses = new ArrayList<PsiElement>();
|
||||
for (PyExpression expr : superExpressions) {
|
||||
expr = unfoldClass(expr);
|
||||
if (expr instanceof PyReferenceExpression) {
|
||||
final PsiPolyVariantReference ref = ((PyReferenceExpression)expr).getReference(PyResolveContext.noProperties());
|
||||
final PsiElement result = ref.resolve();
|
||||
if (result != null) {
|
||||
superClasses.add(result);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
superClasses.add(null);
|
||||
superClasses.add(classElementFromExpression(expr));
|
||||
}
|
||||
return PsiUtilCore.toPsiElementArray(superClasses);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement classElementFromExpression(@NotNull PyExpression expression) {
|
||||
expression = unfoldClass(expression);
|
||||
if (expression instanceof PyReferenceExpression) {
|
||||
final PsiPolyVariantReference ref = ((PyReferenceExpression)expression).getReference(PyResolveContext.noProperties());
|
||||
return ref.resolve();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PyExpression unfoldClass(PyExpression expression) {
|
||||
if (expression instanceof PyCallExpression) {
|
||||
PyCallExpression call = (PyCallExpression)expression;
|
||||
@@ -266,9 +268,19 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
List<PyClassRef> result = resolveSuperClassesFromStub();
|
||||
if (result == null) {
|
||||
result = new ArrayList<PyClassRef>();
|
||||
PsiElement[] superClassElements = getSuperClassElements();
|
||||
for (PsiElement element : superClassElements) {
|
||||
result.add(new PyClassRef(element));
|
||||
final TypeEvalContext context = TypeEvalContext.fastStubOnly(null);
|
||||
final PyExpression[] superClassExpressions = getSuperClassExpressions();
|
||||
for (PyExpression expression : superClassExpressions) {
|
||||
final PsiElement element = classElementFromExpression(expression);
|
||||
if (element != null) {
|
||||
result.add(new PyClassRef(element));
|
||||
}
|
||||
else {
|
||||
final PyType type = expression.getType(context);
|
||||
if (type instanceof PyClassType) {
|
||||
result.add(new PyClassRef((PyClassType)type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,23 +156,25 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
|
||||
|
||||
for (PyClassRef superClass : myClass.iterateAncestors()) {
|
||||
final PyClass pyClass = superClass.getPyClass();
|
||||
final PsiElement element = superClass.getElement();
|
||||
final PyClassType type = superClass.getType();
|
||||
if (pyClass != null) {
|
||||
PsiElement superMember = resolveClassMember(pyClass, myIsDefinition, name, null);
|
||||
if (superMember != null) {
|
||||
return ResolveResultList.to(superMember);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final PsiElement element = superClass.getElement();
|
||||
if (element != null) {
|
||||
for (PyTypeProvider typeProvider : Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
|
||||
final PyType refType = typeProvider.getReferenceType(element, resolveContext.getTypeEvalContext(), myClass);
|
||||
if (refType != null) {
|
||||
return refType.resolveMember(name, location, direction, resolveContext);
|
||||
}
|
||||
else if (element != null) {
|
||||
for (PyTypeProvider typeProvider : Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
|
||||
final PyType refType = typeProvider.getReferenceType(element, resolveContext.getTypeEvalContext(), myClass);
|
||||
if (refType != null) {
|
||||
return refType.resolveMember(name, location, direction, resolveContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type != null) {
|
||||
return type.resolveMember(name, location, direction, resolveContext);
|
||||
}
|
||||
}
|
||||
if (isDefinition() && myClass.isNewStyleClass()) {
|
||||
PyClassType typeType = getMetaclassType();
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
from collections import namedtuple
|
||||
|
||||
Point = namedtuple('Point', ['x', 'y'], verbose=True)
|
||||
|
||||
print Point.x, Point.y
|
||||
|
||||
p = Point(11, y=22)
|
||||
print p.x + p.y
|
||||
print p.__add__
|
||||
print p._asdict()
|
||||
print Point._fields
|
||||
print p._replace
|
||||
|
||||
if isinstance(p, Point):
|
||||
p.x
|
||||
|
||||
class C(namedtuple('C', 'x')):
|
||||
def f(self):
|
||||
return self
|
||||
|
||||
c = C()
|
||||
print(c.x, c.f())
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
from b import Foo
|
||||
|
||||
foo = Foo()
|
||||
print(foo.bar, foo.<warning descr="Unresolved attribute reference 'baz' for class 'Foo'">baz</warning>)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
Foo = namedtuple('Foo', 'bar spam')
|
||||
@@ -0,0 +1,4 @@
|
||||
from b import Bar
|
||||
|
||||
bar = Bar()
|
||||
print(bar.eggs, bar.<warning descr="Unresolved attribute reference 'bacon' for class 'Bar'">bacon</warning>)
|
||||
@@ -0,0 +1,5 @@
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class Bar(namedtuple('Bar', 'spam eggs')):
|
||||
pass
|
||||
@@ -0,0 +1,22 @@
|
||||
from collections import namedtuple
|
||||
|
||||
Point = namedtuple('Point', ['x', 'y'], verbose=True)
|
||||
|
||||
print(Point.x, Point.y)
|
||||
|
||||
p = Point(11, y=22)
|
||||
print(p.x + p.y + p.<warning descr="Unresolved attribute reference 'z' for class 'Point'">z</warning>)
|
||||
print(p.__add__)
|
||||
print(p._asdict())
|
||||
print(Point._fields)
|
||||
print(p._replace)
|
||||
|
||||
if isinstance(p, Point):
|
||||
p.x
|
||||
|
||||
class C(namedtuple('C', 'x y')):
|
||||
def f(self):
|
||||
return self
|
||||
|
||||
c = C()
|
||||
print(c.x, c.y, c.<warning descr="Unresolved attribute reference 'z' for class 'C'">z</warning>, c.f())
|
||||
+20
-5
@@ -16,8 +16,19 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase {
|
||||
return ourPy3Descriptor;
|
||||
}
|
||||
|
||||
private void doMultiFileTest(@NotNull final String filename, @NotNull LanguageLevel level) {
|
||||
runWithLanguageLevel(level, new Runnable() {
|
||||
private void doTest() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON33, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myFixture.configureByFile(TEST_DIRECTORY + getTestName(true) + ".py");
|
||||
myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doMultiFileTest(@NotNull final String filename) {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON33, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final String testName = getTestName(false);
|
||||
@@ -29,11 +40,15 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
private void doMultiFileTest(@NotNull String filename) {
|
||||
doMultiFileTest(filename, LanguageLevel.PYTHON33);
|
||||
public void testNamedTuple() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNamedTupleStub() {
|
||||
public void testNamedTupleAssignment() {
|
||||
doMultiFileTest("a.py");
|
||||
}
|
||||
|
||||
public void testNamedTupleBaseStub() {
|
||||
doMultiFileTest("a.py");
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -58,10 +58,6 @@ public class PyUnresolvedReferencesInspectionTest extends PyTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNamedTuple() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-2308
|
||||
public void testTypeAssertions() {
|
||||
doTest();
|
||||
|
||||
Reference in New Issue
Block a user