From ea36958ed7cee1ceacf60890a0e5bf99a72d49a9 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 20 Jan 2014 14:58:22 +0400 Subject: [PATCH 01/12] Extracted PyStdlibTypeProvider.getNamedTupleType() --- .../stdlib/PyStdlibTypeProvider.java | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index cf6b9ef19301..f203d116f075 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -53,31 +53,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Override public PyType getReferenceType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context, @Nullable PsiElement anchor) { - if (referenceTarget instanceof PyTargetExpression) { - final PyTargetExpression target = (PyTargetExpression)referenceTarget; - final QualifiedName calleeName = target.getCalleeName(); - if (calleeName != null && PyNames.NAMEDTUPLE.equals(calleeName.toString())) { - // TODO: Create stubs for namedtuple for preventing switch from stub to AST - final PyExpression value = target.findAssignedValue(); - if (value instanceof PyCallExpression) { - final PyCallExpression call = (PyCallExpression)value; - final PyCallExpression.PyMarkedCallee callee = call.resolveCallee(PyResolveContext.noImplicits()); - if (callee != null) { - final Callable callable = callee.getCallable(); - if (PyNames.COLLECTIONS_NAMEDTUPLE.equals(callable.getQualifiedName())) { - return PyNamedTupleType.fromCall(call, 1); - } - } - } - } - } - else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { - final PyFunction function = (PyFunction)referenceTarget; - if (PyNames.NAMEDTUPLE.equals(function.getName()) && PyNames.COLLECTIONS_NAMEDTUPLE.equals(function.getQualifiedName())) { - return PyNamedTupleType.fromCall((PyCallExpression)anchor, 2); - } - } - return null; + return getNamedTupleType(referenceTarget, anchor); } @Nullable @@ -126,6 +102,35 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { return null; } + @Nullable + private static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, @Nullable PsiElement anchor) { + if (referenceTarget instanceof PyTargetExpression) { + final PyTargetExpression target = (PyTargetExpression)referenceTarget; + final QualifiedName calleeName = target.getCalleeName(); + if (calleeName != null && PyNames.NAMEDTUPLE.equals(calleeName.toString())) { + // TODO: Create stubs for namedtuple for preventing switch from stub to AST + final PyExpression value = target.findAssignedValue(); + if (value instanceof PyCallExpression) { + final PyCallExpression call = (PyCallExpression)value; + final PyCallExpression.PyMarkedCallee callee = call.resolveCallee(PyResolveContext.noImplicits()); + if (callee != null) { + final Callable callable = callee.getCallable(); + if (PyNames.COLLECTIONS_NAMEDTUPLE.equals(callable.getQualifiedName())) { + return PyNamedTupleType.fromCall(call, 1); + } + } + } + } + } + else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { + final PyFunction function = (PyFunction)referenceTarget; + if (PyNames.NAMEDTUPLE.equals(function.getName()) && PyNames.COLLECTIONS_NAMEDTUPLE.equals(function.getQualifiedName())) { + return PyNamedTupleType.fromCall((PyCallExpression)anchor, 2); + } + } + return null; + } + @Nullable private static PyType getOpenFunctionType(@NotNull String callQName, @NotNull Map arguments, From bf9a384d08e933bd29d42eab95d1f302ebbba65a Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 20 Jan 2014 23:07:05 +0400 Subject: [PATCH 02/12] Refactored PyUtil.getMetaClass() into PyClass.getMetaClassType() This will allow to deal with metaclasses that are not necessary based on PSI elements. Also it will help creating stubs for Python 3 metaclasses defined as keyword arguments. --- .../src/com/jetbrains/python/psi/PyClass.java | 6 +++ .../src/com/jetbrains/python/psi/PyUtil.java | 45 ---------------- .../python/psi/impl/PyClassImpl.java | 52 +++++++++++++++++++ .../python/psi/types/PyClassTypeImpl.java | 12 ++--- 4 files changed, 64 insertions(+), 51 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index e1084e826741..6c0ae56bf2ab 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -216,4 +216,10 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine boolean processClassLevelDeclarations(@NotNull PsiScopeProcessor processor); boolean processInstanceLevelDeclarations(@NotNull PsiScopeProcessor processor, @Nullable PsiElement location); + + /** + * Returns the type representing the metaclass of the class if it is explicitly set, null otherwise. + */ + @Nullable + PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context); } diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 0cd299422694..f0304c3eca4c 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -61,8 +61,6 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; -import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.resolve.QualifiedResolveResult; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHelper; import org.jetbrains.annotations.NonNls; @@ -1203,49 +1201,6 @@ public class PyUtil { return valuesLength; } - @Nullable - public static PyClass getMetaClass(@NotNull final PyClass pyClass) { - final PyTargetExpression metaClassAttribute = pyClass.findClassAttribute(PyNames.DUNDER_METACLASS, false); - if (metaClassAttribute != null) { - final PyExpression expression = metaClassAttribute.findAssignedValue(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) return metaclass; - } - final PsiFile containingFile = pyClass.getContainingFile(); - if (containingFile instanceof PyFile) { - final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS); - if (element instanceof PyTargetExpression) { - final PyExpression expression = ((PyTargetExpression)element).findAssignedValue(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) return metaclass; - } - } - - if (LanguageLevel.forElement(pyClass).isPy3K()) { - final PyExpression[] superClassExpressions = pyClass.getSuperClassExpressions(); - for (PyExpression superClassExpression : superClassExpressions) { - if (superClassExpression instanceof PyKeywordArgument && - PyNames.METACLASS.equals(((PyKeywordArgument)superClassExpression).getKeyword())) { - final PyExpression expression = ((PyKeywordArgument)superClassExpression).getValueExpression(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) return metaclass; - } - } - } - return null; - } - - @Nullable - private static PyClass getMetaFromExpression(final PyExpression metaclass) { - if (metaclass instanceof PyReferenceExpression) { - final QualifiedResolveResult result = ((PyReferenceExpression)metaclass).followAssignmentsChain(PyResolveContext.noImplicits()); - if (result.getElement() instanceof PyClass) { - return (PyClass)result.getElement(); - } - } - return null; - } - @Nullable public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class ... toSkip) { PsiElement element = psiFile.findElementAt(caretOffset); diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 0a9959d23f64..721b150849f9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -40,8 +40,10 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.documentation.DocStringUtil; import com.jetbrains.python.psi.*; +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.QualifiedResolveResult; import com.jetbrains.python.psi.stubs.PropertyStubStorage; import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.stubs.PyFunctionStub; @@ -1167,6 +1169,56 @@ public class PyClassImpl extends PyPresentableElementImpl implement return manager.getParameterizedCachedValue(this, myCachedValueKey, myCachedAncestorsProvider, false, context); } + @Nullable + @Override + public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context) { + final PyTargetExpression metaClassAttribute = findClassAttribute(PyNames.DUNDER_METACLASS, false); + if (metaClassAttribute != null) { + final PyExpression expression = metaClassAttribute.findAssignedValue(); + final PyClass metaclass = getMetaFromExpression(expression); + if (metaclass != null) { + return new PyClassTypeImpl(metaclass, false); + } + } + final PsiFile containingFile = getContainingFile(); + if (containingFile instanceof PyFile) { + final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS); + if (element instanceof PyTargetExpression) { + final PyExpression expression = ((PyTargetExpression)element).findAssignedValue(); + final PyClass metaclass = getMetaFromExpression(expression); + if (metaclass != null) { + return new PyClassTypeImpl(metaclass, false); + } + } + } + + if (LanguageLevel.forElement(this).isPy3K()) { + final PyExpression[] superClassExpressions = getSuperClassExpressions(); + for (PyExpression superClassExpression : superClassExpressions) { + if (superClassExpression instanceof PyKeywordArgument && + PyNames.METACLASS.equals(((PyKeywordArgument)superClassExpression).getKeyword())) { + final PyExpression expression = ((PyKeywordArgument)superClassExpression).getValueExpression(); + final PyClass metaclass = getMetaFromExpression(expression); + if (metaclass != null) { + return new PyClassTypeImpl(metaclass, false); + } + } + } + } + return null; + } + + @Nullable + private static PyClass getMetaFromExpression(final PyExpression metaclass) { + if (metaclass instanceof PyReferenceExpression) { + final QualifiedResolveResult result = ((PyReferenceExpression)metaclass).followAssignmentsChain(PyResolveContext.noImplicits()); + if (result.getElement() instanceof PyClass) { + return (PyClass)result.getElement(); + } + } + return null; + } + @NotNull private List getMROAncestorTypes(@NotNull TypeEvalContext context) { final PyType thisType = context.getType(this); diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 7be077f8bc17..7d400bbb80a4 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -216,7 +216,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } if (isDefinition() && myClass.isNewStyleClass()) { - PyClassType typeType = getMetaclassType(); + final PyClassLikeType typeType = getMetaclassType(context); if (typeType != null) { List typeMembers = typeType.resolveMember(name, location, direction, resolveContext); if (typeMembers != null && !typeMembers.isEmpty()) { @@ -252,10 +252,10 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Nullable - private PyClassType getMetaclassType() { - final PyClass metaClass = PyUtil.getMetaClass(myClass); - if (metaClass != null) { - return new PyClassTypeImpl(metaClass, false); + private PyClassLikeType getMetaclassType(@NotNull TypeEvalContext context) { + final PyClassLikeType type = myClass.getMetaClassType(context); + if (type != null) { + return type; } return PyBuiltinCache.getInstance(myClass).getObjectType("type"); } @@ -395,7 +395,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } if (isDefinition() && myClass.isNewStyleClass()) { - final PyClassType typeType = getMetaclassType(); + final PyClassLikeType typeType = getMetaclassType(typeEvalContext); if (typeType != null) { Collections.addAll(ret, typeType.getCompletionVariants(prefix, location, context)); } From 92107d2468dc106ed5112d3af4a6657f0ff9612e Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 20 Jan 2014 23:33:51 +0400 Subject: [PATCH 03/12] Check __metaclass__ attribute only for Python 2.x --- .../python/psi/impl/PyClassImpl.java | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 721b150849f9..80aa293fd706 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1172,27 +1172,8 @@ public class PyClassImpl extends PyPresentableElementImpl implement @Nullable @Override public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context) { - final PyTargetExpression metaClassAttribute = findClassAttribute(PyNames.DUNDER_METACLASS, false); - if (metaClassAttribute != null) { - final PyExpression expression = metaClassAttribute.findAssignedValue(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) { - return new PyClassTypeImpl(metaclass, false); - } - } - final PsiFile containingFile = getContainingFile(); - if (containingFile instanceof PyFile) { - final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS); - if (element instanceof PyTargetExpression) { - final PyExpression expression = ((PyTargetExpression)element).findAssignedValue(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) { - return new PyClassTypeImpl(metaclass, false); - } - } - } - - if (LanguageLevel.forElement(this).isPy3K()) { + final LanguageLevel level = LanguageLevel.forElement(this); + if (level.isAtLeast(LanguageLevel.PYTHON30)) { final PyExpression[] superClassExpressions = getSuperClassExpressions(); for (PyExpression superClassExpression : superClassExpressions) { if (superClassExpression instanceof PyKeywordArgument && @@ -1205,6 +1186,27 @@ public class PyClassImpl extends PyPresentableElementImpl implement } } } + else { + final PyTargetExpression metaClassAttribute = findClassAttribute(PyNames.DUNDER_METACLASS, false); + if (metaClassAttribute != null) { + final PyExpression expression = metaClassAttribute.findAssignedValue(); + final PyClass metaclass = getMetaFromExpression(expression); + if (metaclass != null) { + return new PyClassTypeImpl(metaclass, false); + } + } + final PsiFile containingFile = getContainingFile(); + if (containingFile instanceof PyFile) { + final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS); + if (element instanceof PyTargetExpression) { + final PyExpression expression = ((PyTargetExpression)element).findAssignedValue(); + final PyClass metaclass = getMetaFromExpression(expression); + if (metaclass != null) { + return new PyClassTypeImpl(metaclass, false); + } + } + } + } return null; } From 48b90c2f22f2de501e70541bcda5c7a15c730c0f Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 20 Jan 2014 23:58:55 +0400 Subject: [PATCH 04/12] Evaluate metaclass type using TypeEvalContext It will try custom type providers for evaluating the metaclass type. --- .../python/psi/impl/PyClassImpl.java | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 80aa293fd706..16623a233a5c 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -40,10 +40,8 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.documentation.DocStringUtil; import com.jetbrains.python.psi.*; -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.QualifiedResolveResult; import com.jetbrains.python.psi.stubs.PropertyStubStorage; import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.stubs.PyFunctionStub; @@ -1174,14 +1172,16 @@ public class PyClassImpl extends PyPresentableElementImpl implement public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context) { final LanguageLevel level = LanguageLevel.forElement(this); if (level.isAtLeast(LanguageLevel.PYTHON30)) { + // TODO: This requires switching from stubs to AST final PyExpression[] superClassExpressions = getSuperClassExpressions(); for (PyExpression superClassExpression : superClassExpressions) { - if (superClassExpression instanceof PyKeywordArgument && - PyNames.METACLASS.equals(((PyKeywordArgument)superClassExpression).getKeyword())) { - final PyExpression expression = ((PyKeywordArgument)superClassExpression).getValueExpression(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) { - return new PyClassTypeImpl(metaclass, false); + if (superClassExpression instanceof PyKeywordArgument) { + final PyKeywordArgument argument = (PyKeywordArgument)superClassExpression; + if (PyNames.METACLASS.equals(argument.getKeyword())) { + final PyClassLikeType type = getClassLikeType(argument, context); + if (type != null) { + return type; + } } } } @@ -1189,20 +1189,18 @@ public class PyClassImpl extends PyPresentableElementImpl implement else { final PyTargetExpression metaClassAttribute = findClassAttribute(PyNames.DUNDER_METACLASS, false); if (metaClassAttribute != null) { - final PyExpression expression = metaClassAttribute.findAssignedValue(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) { - return new PyClassTypeImpl(metaclass, false); + final PyClassLikeType type = getClassLikeType(metaClassAttribute, context); + if (type != null) { + return type; } } final PsiFile containingFile = getContainingFile(); if (containingFile instanceof PyFile) { final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS); - if (element instanceof PyTargetExpression) { - final PyExpression expression = ((PyTargetExpression)element).findAssignedValue(); - final PyClass metaclass = getMetaFromExpression(expression); - if (metaclass != null) { - return new PyClassTypeImpl(metaclass, false); + if (element instanceof PyTypedElement) { + final PyClassLikeType type = getClassLikeType((PyTypedElement)element, context); + if (type != null) { + return type; } } } @@ -1211,12 +1209,10 @@ public class PyClassImpl extends PyPresentableElementImpl implement } @Nullable - private static PyClass getMetaFromExpression(final PyExpression metaclass) { - if (metaclass instanceof PyReferenceExpression) { - final QualifiedResolveResult result = ((PyReferenceExpression)metaclass).followAssignmentsChain(PyResolveContext.noImplicits()); - if (result.getElement() instanceof PyClass) { - return (PyClass)result.getElement(); - } + private static PyClassLikeType getClassLikeType(@NotNull PyTypedElement element, @NotNull TypeEvalContext context) { + final PyType type = context.getType(element); + if (type instanceof PyClassLikeType) { + return (PyClassLikeType)type; } return null; } From 1ee1c255cbb2ea106b0a01260488843f86eaed2a Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 00:29:20 +0400 Subject: [PATCH 05/12] Extracted getMetaClassExpression() --- .../python/psi/impl/PyClassImpl.java | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 16623a233a5c..7ebff88ca682 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1170,49 +1170,43 @@ public class PyClassImpl extends PyPresentableElementImpl implement @Nullable @Override public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context) { - final LanguageLevel level = LanguageLevel.forElement(this); - if (level.isAtLeast(LanguageLevel.PYTHON30)) { - // TODO: This requires switching from stubs to AST - final PyExpression[] superClassExpressions = getSuperClassExpressions(); - for (PyExpression superClassExpression : superClassExpressions) { - if (superClassExpression instanceof PyKeywordArgument) { - final PyKeywordArgument argument = (PyKeywordArgument)superClassExpression; - if (PyNames.METACLASS.equals(argument.getKeyword())) { - final PyClassLikeType type = getClassLikeType(argument, context); - if (type != null) { - return type; - } - } - } - } - } - else { - final PyTargetExpression metaClassAttribute = findClassAttribute(PyNames.DUNDER_METACLASS, false); - if (metaClassAttribute != null) { - final PyClassLikeType type = getClassLikeType(metaClassAttribute, context); - if (type != null) { - return type; - } - } - final PsiFile containingFile = getContainingFile(); - if (containingFile instanceof PyFile) { - final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS); - if (element instanceof PyTypedElement) { - final PyClassLikeType type = getClassLikeType((PyTypedElement)element, context); - if (type != null) { - return type; - } - } + final PyExpression expression = getMetaClassExpression(); + if (expression != null) { + final PyType type = context.getType(expression); + if (type instanceof PyClassLikeType) { + return (PyClassLikeType)type; } } return null; } @Nullable - private static PyClassLikeType getClassLikeType(@NotNull PyTypedElement element, @NotNull TypeEvalContext context) { - final PyType type = context.getType(element); - if (type instanceof PyClassLikeType) { - return (PyClassLikeType)type; + public PyExpression getMetaClassExpression() { + final LanguageLevel level = LanguageLevel.forElement(this); + if (level.isAtLeast(LanguageLevel.PYTHON30)) { + // Requires AST access + for (PyExpression expression : getSuperClassExpressions()) { + if (expression instanceof PyKeywordArgument) { + final PyKeywordArgument argument = (PyKeywordArgument)expression; + if (PyNames.METACLASS.equals(argument.getKeyword())) { + return argument.getValueExpression(); + } + } + } + } + else { + final PyTargetExpression attribute = findClassAttribute(PyNames.DUNDER_METACLASS, false); + if (attribute != null) { + return attribute; + } + final PsiFile file = getContainingFile(); + if (file instanceof PyFile) { + final PyFile pyFile = (PyFile)file; + final PsiElement element = pyFile.getElementNamed(PyNames.DUNDER_METACLASS); + if (element instanceof PyExpression) { + return (PyExpression)element; + } + } } return null; } From cb9e823882035d7577abbac0e5de03dd26f545e1 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 00:33:01 +0400 Subject: [PATCH 06/12] Added PyClass.getMetaClassExpression() --- python/psi-api/src/com/jetbrains/python/psi/PyClass.java | 9 +++++++++ .../src/com/jetbrains/python/psi/impl/PyClassImpl.java | 1 + 2 files changed, 10 insertions(+) diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index 6c0ae56bf2ab..723214fa2e1b 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -222,4 +222,13 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine */ @Nullable PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context); + + /** + * Returns the expression that defines the metaclass of the class. + * + * It might be defined outside the class in case of file-level __metaclass__ attributes. + * Operates at the AST level. + */ + @Nullable + PyExpression getMetaClassExpression(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 7ebff88ca682..6928810ccbfe 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1181,6 +1181,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement } @Nullable + @Override public PyExpression getMetaClassExpression() { final LanguageLevel level = LanguageLevel.forElement(this); if (level.isAtLeast(LanguageLevel.PYTHON30)) { From 6238d926c2ea47513212daa0a61041013aa40cac Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 00:51:59 +0400 Subject: [PATCH 07/12] Store metaclass qualified name in class stub for better performance --- .../python/psi/stubs/PyClassStub.java | 4 +++- .../python/psi/PyFileElementType.java | 2 +- .../python/psi/impl/PyClassImpl.java | 23 +++++++++++++++---- .../psi/impl/stubs/PyClassElementType.java | 9 +++++++- .../psi/impl/stubs/PyClassStubImpl.java | 13 +++++++++-- .../MetaclassStub/a.py | 5 ++++ .../MetaclassStub/m1.py | 7 ++++++ ...Py3UnresolvedReferencesInspectionTest.java | 20 ++++++++++++++++ 8 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/a.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/m1.py diff --git a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java index 02b212c725f0..2ca1e2a9c7c9 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java +++ b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java @@ -20,13 +20,15 @@ package com.jetbrains.python.psi.stubs; import com.intellij.psi.stubs.NamedStub; -import com.jetbrains.python.psi.PyClass; import com.intellij.psi.util.QualifiedName; +import com.jetbrains.python.psi.PyClass; +import org.jetbrains.annotations.Nullable; import java.util.List; public interface PyClassStub extends NamedStub { QualifiedName[] getSuperClasses(); + @Nullable QualifiedName getMetaClass(); List getSlots(); String getDocString(); } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index 07d87c91ccf2..150745e2dbbb 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -63,7 +63,7 @@ public class PyFileElementType extends IStubFileElementType { @Override public int getStubVersion() { // Don't forget to update versions of indexes that use the updated stub-based elements - return 48; + return 49; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 6928810ccbfe..1346f9423348 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1170,11 +1170,24 @@ public class PyClassImpl extends PyPresentableElementImpl implement @Nullable @Override public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context) { - final PyExpression expression = getMetaClassExpression(); - if (expression != null) { - final PyType type = context.getType(expression); - if (type instanceof PyClassLikeType) { - return (PyClassLikeType)type; + if (context.maySwitchToAST(this)) { + final PyExpression expression = getMetaClassExpression(); + if (expression != null) { + final PyType type = context.getType(expression); + if (type instanceof PyClassLikeType) { + return (PyClassLikeType)type; + } + } + } + else { + final PyClassStub stub = getStub(); + final QualifiedName name = stub != null ? stub.getMetaClass() : PyQualifiedNameFactory.fromExpression(getMetaClassExpression()); + final PsiFile file = getContainingFile(); + if (file instanceof PyFile) { + final PyFile pyFile = (PyFile)file; + if (name != null) { + return classTypeFromQName(name, pyFile, context); + } } } return null; diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java index 813fb5c3c260..e29c71c092be 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java @@ -59,12 +59,16 @@ public class PyClassElementType extends PyStubElementType final PyExpression[] exprs = psi.getSuperClassExpressions(); List superClasses = new ArrayList(); for (PyExpression expression : exprs) { + if (expression instanceof PyKeywordArgument) { + continue; + } expression = PyClassImpl.unfoldClass(expression); superClasses.add(PyQualifiedNameFactory.fromExpression(expression)); } final PyStringLiteralExpression docStringExpression = psi.getDocStringExpression(); return new PyClassStubImpl(psi.getName(), parentStub, superClasses.toArray(new QualifiedName[superClasses.size()]), + PyQualifiedNameFactory.fromExpression(psi.getMetaClassExpression()), psi.getOwnSlots(), PyPsiUtils.strValue(docStringExpression), getStubElementType()); @@ -77,6 +81,7 @@ public class PyClassElementType extends PyStubElementType for (QualifiedName s : classes) { QualifiedName.serialize(s, dataStream); } + QualifiedName.serialize(pyClassStub.getMetaClass(), dataStream); PyFileElementType.writeNullableList(dataStream, pyClassStub.getSlots()); final String docString = pyClassStub.getDocString(); dataStream.writeUTFFast(docString != null ? docString : ""); @@ -90,9 +95,11 @@ public class PyClassElementType extends PyStubElementType for (int i = 0; i < superClassCount; i++) { superClasses[i] = QualifiedName.deserialize(dataStream); } + final QualifiedName metaClass = QualifiedName.deserialize(dataStream); List slots = PyFileElementType.readNullableList(dataStream); final String docString = dataStream.readUTFFast(); - return new PyClassStubImpl(name, parentStub, superClasses, slots, docString.length() > 0 ? docString : null, getStubElementType()); + return new PyClassStubImpl(name, parentStub, superClasses, metaClass, slots, docString.length() > 0 ? docString : null, + getStubElementType()); } public void indexStub(@NotNull final PyClassStub stub, @NotNull final IndexSink sink) { diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java index e607478ba46f..0de91810d192 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java @@ -21,6 +21,7 @@ import com.intellij.psi.stubs.StubElement; import com.jetbrains.python.psi.PyClass; import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.psi.stubs.PyClassStub; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -30,14 +31,16 @@ import java.util.List; public class PyClassStubImpl extends StubBase implements PyClassStub { private final String myName; private final QualifiedName[] mySuperClasses; + @Nullable private final QualifiedName myMetaClass; private final List mySlots; private final String myDocString; - public PyClassStubImpl(final String name, StubElement parentStub, final QualifiedName[] superClasses, final List slots, - String docString, IStubElementType stubElementType) { + public PyClassStubImpl(final String name, StubElement parentStub, final QualifiedName[] superClasses, @Nullable QualifiedName metaClass, + final List slots, String docString, IStubElementType stubElementType) { super(parentStub, stubElementType); myName = name; mySuperClasses = superClasses; + myMetaClass = metaClass; mySlots = slots; myDocString = docString; } @@ -50,6 +53,12 @@ public class PyClassStubImpl extends StubBase implements PyClassStub { return mySuperClasses; } + @Nullable + @Override + public QualifiedName getMetaClass() { + return myMetaClass; + } + @Override public List getSlots() { return mySlots; diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/a.py new file mode 100644 index 000000000000..bf0ee4488340 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/a.py @@ -0,0 +1,5 @@ +from m1 import C + +c = C() +print(C.foo(), C.bar()) +print(c.foo()) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/m1.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/m1.py new file mode 100644 index 000000000000..bd6222aa73fd --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/MetaclassStub/m1.py @@ -0,0 +1,7 @@ +class M(type): + def foo(cls): + pass + + +class C(metaclass=M): + pass diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 65cb17d2ff63..69d8455080f0 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -15,11 +15,19 @@ */ package com.jetbrains.python.inspections; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.LightProjectDescriptor; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.stubs.PyClassNameIndex; import org.jetbrains.annotations.NotNull; +import java.util.Collection; + /** * @author vlan */ @@ -73,6 +81,18 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase { doTest(); } + public void testMetaclassStub() { + doMultiFileTest("a.py"); + final Project project = myFixture.getProject(); + Collection classes = PyClassNameIndex.find("M", project, GlobalSearchScope.allScope(project)); + for (PyClass cls : classes) { + final PsiFile file = cls.getContainingFile(); + if (file instanceof PyFile) { + assertNotParsed((PyFile)file); + } + } + } + // PY-9011 public void testDatetimeDateAttributesOutsideClass() { doMultiFileTest("a.py"); From 14c2396911393d551ddcae00fc38d097c9e4e361 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 01:22:34 +0400 Subject: [PATCH 08/12] Type provider for enum.Enum members --- .../stdlib/PyStdlibTypeProvider.java | 34 ++++++++++++++++++- .../EnumMemberAttributes/a.py | 12 +++++++ .../EnumMemberAttributes/enum.py | 17 ++++++++++ ...Py3UnresolvedReferencesInspectionTest.java | 4 +++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/enum.py diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index f203d116f075..5e0734030a16 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -21,6 +21,8 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.PyNames; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +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.PyTypeProvider; @@ -30,6 +32,7 @@ import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.Map; import java.util.Set; @@ -53,7 +56,36 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Override public PyType getReferenceType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context, @Nullable PsiElement anchor) { - return getNamedTupleType(referenceTarget, anchor); + PyType type = getNamedTupleType(referenceTarget, anchor); + if (type != null) { + return type; + } + type = getEnumType(referenceTarget, context); + if (type != null) { + return type; + } + return null; + } + + @Nullable + private static PyType getEnumType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context) { + if (referenceTarget instanceof PyTargetExpression) { + final PyTargetExpression target = (PyTargetExpression)referenceTarget; + final ScopeOwner owner = ScopeUtil.getScopeOwner(target); + if (owner instanceof PyClass) { + final PyClass cls = (PyClass)owner; + final List types = cls.getAncestorTypes(context); + for (PyClassLikeType type : types) { + if ("enum.Enum".equals(type.getClassQName())) { + final PyType classType = context.getType(cls); + if (classType instanceof PyClassType) { + return ((PyClassType)classType).toInstance(); + } + } + } + } + } + return null; } @Nullable diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py new file mode 100644 index 000000000000..36d36b0ec620 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class Color(Enum): + red = 1 + green = 2 + blue = 3 + + +print(Color.red.name, Color.red.name.upper()) +print(Color.red.value, Color.red.value.real) +print(Color.red.foo) diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/enum.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/enum.py new file mode 100644 index 000000000000..368f85a501b9 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/enum.py @@ -0,0 +1,17 @@ +class EnumMeta(type): + """Fake Enum metaclass.""" + @property + def __members__(cls): + return {} + + +class Enum(object, metaclass=EnumMeta): + """Fake Enum class.""" + + @DynamicClassAttribute + def name(self): + return self._name_ + + @DynamicClassAttribute + def value(self): + return self._value_ diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 69d8455080f0..ee37eabc41ee 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -101,4 +101,8 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase { public void testObjectNewAttributes() { doTest(); } + + public void testEnumMemberAttributes() { + doMultiFileTest("a.py"); + } } From 89beff839b647dcf1ed2d4dab070464593d51c91 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 01:36:33 +0400 Subject: [PATCH 09/12] Fixed PyABCUtil.isSubtype() check for class definitions that have metaclasses --- .../python/psi/impl/PyJavaClassType.java | 6 ++++ .../python/psi/types/PyClassLikeType.java | 3 ++ .../inspections/PyArgumentListInspection.java | 4 +-- .../inspections/PyTypeCheckerInspection.java | 2 +- .../jetbrains/python/psi/types/PyABCUtil.java | 18 ++++++++--- .../python/psi/types/PyClassTypeImpl.java | 26 +++++++++++----- .../MetaClassIteration.py | 31 +++++++++++++++++++ .../PyTypeCheckerInspectionTest.java | 4 +++ 8 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java index 94aea76789d6..02176e32c287 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java @@ -148,6 +148,12 @@ public class PyJavaClassType implements PyClassLikeType { return myClass.isValid(); } + @Nullable + @Override + public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context, boolean inherited) { + return null; + } + public PsiClass getPsiClass() { return myClass; } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java index 804195aa978e..a059bc7ca53e 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java @@ -44,4 +44,7 @@ public interface PyClassLikeType extends PyCallableType { boolean inherited); boolean isValid(); + + @Nullable + PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context, boolean inherited); } diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index 6faea31eabcb..9a87959396fe 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -173,12 +173,12 @@ public class PyArgumentListInspection extends PyInspection { PyType inside_type = context.getType(content); if (inside_type != null && !PyTypeChecker.isUnknown(inside_type)) { if (((PyStarArgument)arg).isKeyword()) { - if (!PyABCUtil.isSubtype(inside_type, PyNames.MAPPING)) { + if (!PyABCUtil.isSubtype(inside_type, PyNames.MAPPING, context)) { holder.registerProblem(arg, PyBundle.message("INSP.expected.dict.got.$0", inside_type.getName())); } } else { // * arg - if (!PyABCUtil.isSubtype(inside_type, PyNames.ITERABLE)) { + if (!PyABCUtil.isSubtype(inside_type, PyNames.ITERABLE, context)) { holder.registerProblem(arg, PyBundle.message("INSP.expected.iter.got.$0", inside_type.getName())); } } diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 484ac055c79e..45df382d3f83 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -78,7 +78,7 @@ public class PyTypeCheckerInspection extends PyInspection { final PyExpression source = node.getForPart().getSource(); if (source != null) { final PyType type = myTypeEvalContext.getType(source); - if (type != null && !PyTypeChecker.isUnknown(type) && !PyABCUtil.isSubtype(type, PyNames.ITERABLE)) { + if (type != null && !PyTypeChecker.isUnknown(type) && !PyABCUtil.isSubtype(type, PyNames.ITERABLE, myTypeEvalContext)) { registerProblem(source, String.format("Expected 'collections.Iterable', got '%s' instead", PythonDocumentationProvider.getTypeName(type, myTypeEvalContext))); } diff --git a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java index faf10ba1dc21..fcd3289cbd2f 100644 --- a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java +++ b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java @@ -83,16 +83,26 @@ public class PyABCUtil { return false; } - public static boolean isSubtype(@NotNull PyType type, @NotNull String superClassName) { + public static boolean isSubtype(@NotNull PyType type, @NotNull String superClassName, @NotNull TypeEvalContext context) { if (type instanceof PyClassType) { - final PyClass pyClass = ((PyClassType)type).getPyClass(); - return isSubclass(pyClass, superClassName, true); + final PyClassType classType = (PyClassType)type; + final PyClass pyClass = classType.getPyClass(); + if (classType.isDefinition()) { + final PyClassLikeType metaClassType = classType.getMetaClassType(context, true); + if (metaClassType instanceof PyClassType) { + final PyClassType metaClass = (PyClassType)metaClassType; + return isSubclass(metaClass.getPyClass(), superClassName, true); + } + } + else { + return isSubclass(pyClass, superClassName, true); + } } if (type instanceof PyUnionType) { final PyUnionType unionType = (PyUnionType)type; for (PyType m : unionType.getMembers()) { if (m != null) { - if (!isSubtype(m, superClassName)) { + if (!isSubtype(m, superClassName, context)) { return false; } } diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 7d400bbb80a4..14ec62e157f9 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -216,7 +216,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } if (isDefinition() && myClass.isNewStyleClass()) { - final PyClassLikeType typeType = getMetaclassType(context); + final PyClassLikeType typeType = getMetaClassType(context, inherited); if (typeType != null) { List typeMembers = typeType.resolveMember(name, location, direction, resolveContext); if (typeMembers != null && !typeMembers.isEmpty()) { @@ -252,12 +252,24 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Nullable - private PyClassLikeType getMetaclassType(@NotNull TypeEvalContext context) { - final PyClassLikeType type = myClass.getMetaClassType(context); - if (type != null) { - return type; + @Override + public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context, boolean inherited) { + final PyClassLikeType ownMeta = myClass.getMetaClassType(context); + if (ownMeta != null) { + return ownMeta; } - return PyBuiltinCache.getInstance(myClass).getObjectType("type"); + if (inherited) { + for (PyClassLikeType ancestor : myClass.getAncestorTypes(context)) { + if (ancestor != null) { + final PyClassLikeType ancestorMeta = ancestor.getMetaClassType(context, false); + if (ancestorMeta != null) { + return ancestorMeta; + } + } + } + return PyBuiltinCache.getInstance(myClass).getObjectType("type"); + } + return null; } @Override @@ -395,7 +407,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } if (isDefinition() && myClass.isNewStyleClass()) { - final PyClassLikeType typeType = getMetaclassType(typeEvalContext); + final PyClassLikeType typeType = getMetaClassType(typeEvalContext, true); if (typeType != null) { Collections.addAll(ret, typeType.getCompletionVariants(prefix, location, context)); } diff --git a/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py b/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py new file mode 100644 index 000000000000..0ec69266af7c --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/MetaClassIteration.py @@ -0,0 +1,31 @@ +class M1(type): + def __iter__(self): + pass + + +class M2(type): + pass + + +class C1(object): + __metaclass__ = M1 + + +class C2(object): + __metaclass__ = M2 + + +class B1(C1): + pass + + +for x in C1: + pass + + +for y in C2: + pass + + +for z in B1: + pass diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 2615cc146a80..0e09054e3067 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -228,4 +228,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testSecondFormIter() { doTest(); } + + public void testMetaClassIteration() { + doTest(); + } } From 2bbb843512b94294ee27b0469cfbaaedc78dc58b Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 16:11:19 +0400 Subject: [PATCH 10/12] Types of Enum members' fields and type of Enum.__members__ --- .../stdlib/PyStdlibTypeProvider.java | 33 +++++++++++++++++-- .../EnumMemberAttributes/a.py | 6 ++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 5e0734030a16..d98567f40c43 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -60,7 +60,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { if (type != null) { return type; } - type = getEnumType(referenceTarget, context); + type = getEnumType(referenceTarget, context, anchor); if (type != null) { return type; } @@ -68,7 +68,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getEnumType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context) { + private static PyType getEnumType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { if (referenceTarget instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)referenceTarget; final ScopeOwner owner = ScopeUtil.getScopeOwner(target); @@ -85,6 +86,34 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } } + if (referenceTarget instanceof PyQualifiedNameOwner) { + final PyQualifiedNameOwner qualifiedNameOwner = (PyQualifiedNameOwner)referenceTarget; + final String name = qualifiedNameOwner.getQualifiedName(); + if ("enum.Enum.name".equals(name)) { + return PyBuiltinCache.getInstance(referenceTarget).getStrType(); + } + else if ("enum.Enum.value".equals(name) && anchor instanceof PyReferenceExpression && context.maySwitchToAST(anchor)) { + final PyReferenceExpression anchorExpr = (PyReferenceExpression)anchor; + final PyExpression qualifier = anchorExpr.getQualifier(); + if (qualifier instanceof PyReferenceExpression) { + final PyReferenceExpression qualifierExpr = (PyReferenceExpression)qualifier; + final PsiElement resolvedQualifier = qualifierExpr.getReference().resolve(); + if (resolvedQualifier instanceof PyTargetExpression) { + final PyTargetExpression qualifierTarget = (PyTargetExpression)resolvedQualifier; + // Requires switching to AST, we cannot use getType(qualifierTarget) here, because its type is overridden by this type provider + if (context.maySwitchToAST(qualifierTarget)) { + final PyExpression value = qualifierTarget.findAssignedValue(); + if (value != null) { + return context.getType(value); + } + } + } + } + } + else if ("enum.EnumMeta.__members__".equals(name)) { + return PyTypeParser.getTypeByName(referenceTarget, "dict[str, unknown]"); + } + } return null; } diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py index 36d36b0ec620..9d4ffeb8d729 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/EnumMemberAttributes/a.py @@ -8,5 +8,11 @@ class Color(Enum): print(Color.red.name, Color.red.name.upper()) +print(Color.red.name.foo) print(Color.red.value, Color.red.value.real) +print(Color.red.value.foo) print(Color.red.foo) + + +print(Color.__members__.items()) +print(Color.__members__.foo) From d2a8310ee8e8ef713d9e3bd4034654af0f58c44e Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 21 Jan 2014 16:18:10 +0400 Subject: [PATCH 11/12] Removed redundant PyTypeProvider.getIterationType() It was used only for 'file', superseded by 'io' classes. Providers can provide iteration types via '__iter__' and '__next__'. --- .../jetbrains/python/psi/impl/PyTypeProvider.java | 3 --- .../python/psi/types/PyTypeProviderBase.java | 5 ----- .../codeInsight/stdlib/PyStdlibTypeProvider.java | 12 ------------ .../python/psi/impl/PyTargetExpressionImpl.java | 7 ------- 4 files changed, 27 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/psi/impl/PyTypeProvider.java b/python/psi-api/src/com/jetbrains/python/psi/impl/PyTypeProvider.java index 38a0ee670050..b3ad13c7ef04 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/impl/PyTypeProvider.java +++ b/python/psi-api/src/com/jetbrains/python/psi/impl/PyTypeProvider.java @@ -41,9 +41,6 @@ public interface PyTypeProvider { @Nullable PyType getReturnType(@NotNull PyFunction function, @Nullable PyQualifiedExpression callSite, @NotNull TypeEvalContext context); - @Nullable - PyType getIterationType(@NotNull PyClass iterable); - @Nullable PyType getContextManagerVariableType(PyClass contextManager, PyExpression withExpression, TypeEvalContext context); diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java index dc20bad8f36f..b449e5dd9377 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java @@ -106,11 +106,6 @@ public class PyTypeProviderBase implements PyTypeProvider { return null; } - @Override - public PyType getIterationType(@NotNull PyClass iterable) { - return null; - } - @Nullable @Override public PyType getContextManagerVariableType(PyClass contextManager, PyExpression withExpression, TypeEvalContext context) { diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index d98567f40c43..7aeeca4f6095 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -135,18 +135,6 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { return null; } - @Nullable - @Override - public PyType getIterationType(@NotNull PyClass iterable) { - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(iterable); - if (builtinCache.hasInBuiltins(iterable)) { - if ("file".equals(iterable.getName())) { - return builtinCache.getStrType(); - } - } - return null; - } - @Nullable @Override public PyType getContextManagerVariableType(@NotNull PyClass contextManager, @NotNull PyExpression withExpression, @NotNull TypeEvalContext context) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index fd49c2543cc7..3a63428c69e6 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -392,13 +392,6 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl Date: Tue, 21 Jan 2014 17:45:38 +0400 Subject: [PATCH 12/12] Calculate iteration type via types, not PyClass elements --- .../psi/impl/PyTargetExpressionImpl.java | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index 3a63428c69e6..19d42a017113 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -356,7 +356,7 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl members = ((PyUnionType)iterableType).getMembers(); final List iterationTypes = new ArrayList(); for (PyType member : members) { - iterationTypes.add(getIterationType(member, source, context)); + iterationTypes.add(getIterationType(member, source, anchor, context)); } return PyUnionType.union(iterationTypes); } - else if (iterableType instanceof PyClassType) { - final PyClass pyClass = ((PyClassType)iterableType).getPyClass(); - if (PyABCUtil.isSubclass(pyClass, PyNames.ITERATOR)) { - final PyFunction iterateMethod = pyClass.findMethodByName(PyNames.ITER, true); - PyType iterateMethodType = null; - if (iterateMethod != null) { - iterateMethodType = getContextSensitiveType(iterateMethod, context, source); + else if (iterableType != null && PyABCUtil.isSubtype(iterableType, PyNames.ITERATOR, context)) { + final PyFunction iterateMethod = findMethodByName(iterableType, PyNames.ITER, context); + PyType iterateMethodType = null; + if (iterateMethod != null) { + iterateMethodType = getContextSensitiveType(iterateMethod, context, source); + } + if (iterateMethodType instanceof PyCollectionType) { + final PyCollectionType collectionType = (PyCollectionType)iterateMethodType; + result = collectionType.getElementType(context); + } + if (result == null) { + final String nextMethodName = LanguageLevel.forElement(anchor).isAtLeast(LanguageLevel.PYTHON30) ? + PyNames.DUNDER_NEXT : PyNames.NEXT; + final PyFunction next = findMethodByName(iterableType, nextMethodName, context); + if (next != null) { + result = getContextSensitiveType(next, context, source); } - if (iterateMethodType instanceof PyCollectionType) { - final PyCollectionType collectionType = (PyCollectionType)iterateMethodType; - result = collectionType.getElementType(context); - } - if (result == null) { - PyFunction next = pyClass.findMethodByName(PyNames.NEXT, true); - if (next == null) { - next = pyClass.findMethodByName(PyNames.DUNDER_NEXT, true); - } - if (next != null) { - result = getContextSensitiveType(next, context, source); - } - } - if (result == null) { - final PyFunction getItem = pyClass.findMethodByName(PyNames.GETITEM, true); - if (getItem != null) { - result = getContextSensitiveType(getItem, context, source); - } + } + if (result == null) { + final PyFunction getItem = findMethodByName(iterableType, PyNames.GETITEM, context); + if (getItem != null) { + result = getContextSensitiveType(getItem, context, source); } } } return result; } + @Nullable + private static PyFunction findMethodByName(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) { + final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context); + final List results = type.resolveMember(name, null, AccessDirection.READ, resolveContext); + if (results != null && !results.isEmpty()) { + final RatedResolveResult result = results.get(0); + final PsiElement element = result.getElement(); + if (element instanceof PyFunction) { + return (PyFunction)element; + } + } + return null; + } + @Nullable private static PyType getContextSensitiveType(@NotNull PyFunction function, @NotNull TypeEvalContext context, @Nullable PyExpression source) {