From b3ca9489130c9746ca02600c760c944040c3c77a Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Thu, 28 Jun 2018 21:55:42 +0300 Subject: [PATCH] Reduce code duplication in visitMembers and getMemberNames (PY-30357) And don't process methods, attributes and nested classes separately, they are handled while processing declarations inside class --- .../python/psi/types/PyClassTypeImpl.java | 137 +++++++----------- .../PyTypeCheckerInspectionTest.java | 12 ++ 2 files changed, 63 insertions(+), 86 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index be6046660945..c175606c6538 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -9,9 +9,8 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiInvalidElementAccessException; +import com.intellij.psi.*; +import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.QualifiedName; import com.intellij.util.ArrayUtil; @@ -35,7 +34,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.jetbrains.python.psi.PyUtil.as; import static com.jetbrains.python.psi.resolve.PyResolveImportUtil.fromFoothold; @@ -621,45 +623,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Override - public void visitMembers(@NotNull final Processor processor, - final boolean inherited, - @NotNull final TypeEvalContext context) { - myClass.visitMethods(new MyProcessorWrapper<>(processor), false, context); - myClass.visitClassAttributes(new MyProcessorWrapper<>(processor), false, context); + public void visitMembers(@NotNull Processor processor, boolean inherited, @NotNull TypeEvalContext context) { + processMembers(processor); - for (PyTargetExpression expression : myClass.getInstanceAttributes()) { - processor.process(expression); - } - - if (!inherited) { - return; - } - - for (final PyClassLikeType type : getAncestorTypes(context)) { - if (type != null) { - // "false" because getAncestorTypes returns ALL ancestors, not only direct parents - type.visitMembers(processor, false, context); - } - } - - visitMetaClassMembers(processor, context); - } - - private void visitMetaClassMembers(@NotNull Processor processor, @NotNull TypeEvalContext context) { - if (!myClass.isNewStyleClass(context)) { - return; - } - - final PyClassLikeType typeType = getMetaClassType(context, true); - if (typeType == null) { - return; - } - - if (isDefinition()) { - typeType.visitMembers(processor, true, context); - } - else if (typeType instanceof PyClassType) { - ((PyClassType)typeType).getPyClass().getInstanceAttributes().forEach(processor::process); + if (inherited) { + prepareAncestorsForProcessingMembers(Function.identity(), context).forEach(type -> type.visitMembers(processor, false, context)); + processMetaClassMembers(typeType -> typeType.visitMembers(processor, true, context), processor, context); } } @@ -671,17 +640,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { final Set result = new LinkedHashSet<>(); - for (PyFunction function : myClass.getMethods()) { - result.add(function.getName()); - } - - for (PyTargetExpression expression : myClass.getClassAttributes()) { - result.add(expression.getName()); - } - - for (PyTargetExpression expression : myClass.getInstanceAttributes()) { - result.add(expression.getName()); - } + processMembers( + element -> { + if (element instanceof PsiNamedElement) ContainerUtil.addIfNotNull(result, ((PsiNamedElement)element).getName()); + return true; + } + ); if (myClass.isNewStyleClass(context)) { result.addAll(ContainerUtil.notNullize(myClass.getOwnSlots())); @@ -694,40 +658,55 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } if (inherited) { - for (PyClassLikeType type : getAncestorTypes(context)) { - if (type != null) { - final PyClassLikeType ancestorType = isDefinition() ? type : type.toInstance(); + prepareAncestorsForProcessingMembers(type -> type.getMemberNames(false, context), context).forEach(result::addAll); - result.addAll(ancestorType.getMemberNames(false, context)); - } - } - - result.addAll(getMetaClassMemberNames(context)); + processMetaClassMembers( + typeType -> result.addAll(typeType.getMemberNames(true, context)), + instanceTypeAttribute -> { + ContainerUtil.addIfNotNull(result, instanceTypeAttribute.getName()); + return true; + }, + context + ); } return result; } + private void processMembers(@NotNull Processor processor) { + final PsiScopeProcessor scopeProcessor = new PsiScopeProcessor() { + @Override + public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { + return processor.process(element); + } + }; + + myClass.processClassLevelDeclarations(scopeProcessor); + myClass.processInstanceLevelDeclarations(scopeProcessor, null); + } + @NotNull - private Set getMetaClassMemberNames(@NotNull TypeEvalContext context) { - if (!myClass.isNewStyleClass(context)) { - return Collections.emptySet(); - } + private Stream prepareAncestorsForProcessingMembers(@NotNull Function ancestorMapper, + @NotNull TypeEvalContext context) { + return StreamEx.of(getAncestorTypes(context)).nonNull().map(type -> isDefinition() ? type : type.toInstance()).map(ancestorMapper); + } + + private void processMetaClassMembers(@NotNull Consumer typeTypeConsumer, + @NotNull Processor instanceTypeAttributesProcessor, + @NotNull TypeEvalContext context) { + if (!myClass.isNewStyleClass(context)) return; final PyClassLikeType typeType = getMetaClassType(context, true); - if (typeType == null) { - return Collections.emptySet(); - } + if (typeType == null) return; if (isDefinition()) { - return typeType.getMemberNames(true, context); + typeTypeConsumer.accept(typeType); } else if (typeType instanceof PyClassType) { - final List typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes(); - return ContainerUtil.map2SetNotNull(typeInstanceAttributes, PyTargetExpression::getName); + for (PyTargetExpression attribute : ((PyClassType)typeType).getPyClass().getInstanceAttributes()) { + if (!instanceTypeAttributesProcessor.process(attribute)) return; + } } - - return Collections.emptySet(); } private void addOwnClassMembers(PsiElement expressionHook, @@ -878,18 +857,4 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } return new PyClassTypeImpl(pyClass, isDefinition); } - - private static final class MyProcessorWrapper implements Processor { - private final Processor myProcessor; - - private MyProcessorWrapper(@NotNull final Processor processor) { - myProcessor = processor; - } - - @Override - public boolean process(final T t) { - myProcessor.process(t); - return true; - } - } } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 5b570d152f0c..b4bccb013938 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -670,4 +670,16 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase { " pass\n") ); } + + // PY-30357 + public void testClassWithNestedAgainstStructural() { + doTestByText("def f(cls):\n" + + " print(cls.Meta)\n" + + "\n" + + "class A:\n" + + " class Meta:\n" + + " pass\n" + + "\n" + + "f(A)"); + } }