From adc2d3859ba142dd933442f401bc992db741e4ac Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 16 Dec 2014 17:17:18 +0300 Subject: [PATCH] Better error messages for checking structural types The new error message mentions the attributes that the actual type is misssing instead of just telling what the expected and actual types are. --- .../inspections/PyTypeCheckerInspection.java | 37 ++++++++++++++++++- .../python/psi/types/PyTypeChecker.java | 2 +- .../ExpectedStructuralType.py | 2 +- .../GetAttributeAgainstStructuralType.py | 2 +- .../StructuralTypesForNestedCalls.py | 2 +- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 61e473da5c23..50230b05c795 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -15,13 +15,16 @@ */ package com.jetbrains.python.inspections; +import com.google.common.collect.Sets; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; +import com.intellij.util.Function; import com.intellij.util.containers.hash.LinkedHashMap; import com.jetbrains.python.PyNames; import com.jetbrains.python.documentation.PythonDocumentationProvider; @@ -32,6 +35,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; +import java.util.Set; /** * @author vlan @@ -126,7 +130,27 @@ public class PyTypeCheckerInspection extends PyInspection { } } final String actualName = PythonDocumentationProvider.getTypeName(actual, context); - final String msg = String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName); + String msg= String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName); + if (expected instanceof PyStructuralType) { + final Set expectedAttributes = ((PyStructuralType)expected).getAttributeNames(); + final Set actualAttributes = getAttributes(actual); + if (actualAttributes != null) { + final Sets.SetView missingAttributes = Sets.difference(expectedAttributes, actualAttributes); + if (missingAttributes.size() == 1) { + msg = String.format("Type '%s' doesn't have expected attribute '%s'", actualName, missingAttributes.iterator().next()); + } + else { + msg = String.format("Type '%s' doesn't have expected attributes %s", + actualName, + StringUtil.join(missingAttributes, new Function() { + @Override + public String fun(String s) { + return String.format("'%s'", s); + } + }, ", ")); + } + } + } registerProblem(node, msg, highlightType); return msg; } @@ -135,6 +159,17 @@ public class PyTypeCheckerInspection extends PyInspection { } } + @Nullable + private static Set getAttributes(@NotNull PyType type) { + if (type instanceof PyStructuralType) { + return ((PyStructuralType)type).getAttributeNames(); + } + else if (type instanceof PyClassType) { + return PyTypeChecker.getClassAttributes(((PyClassType)type).getPyClass(), true); + } + return null; + } + @Override public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder problemsHolder) { if (LOG.isDebugEnabled()) { diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index cec92a9514b3..d5c75466ddd0 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -205,7 +205,7 @@ public class PyTypeChecker { } @NotNull - private static Set getClassAttributes(@NotNull PyClass cls, boolean inherited) { + public static Set getClassAttributes(@NotNull PyClass cls, boolean inherited) { final Set attributes = new HashSet(); for (PyFunction function : cls.getMethods(false)) { attributes.add(function.getName()); diff --git a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py index d550559e1f9a..1080906218ab 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ExpectedStructuralType.py @@ -12,5 +12,5 @@ def test(x): g(x) z = 'foo' - f(z) + f(z) g(z) diff --git a/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py b/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py index 52249d9de37f..617df26bcbcf 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GetAttributeAgainstStructuralType.py @@ -18,4 +18,4 @@ class E(object): f(C()) f(D()) -f(E()) +f(E()) diff --git a/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py b/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py index 5e22d50690c3..1e4ae234bbdf 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py +++ b/python/testData/inspections/PyTypeCheckerInspection/StructuralTypesForNestedCalls.py @@ -7,4 +7,4 @@ def g(x): def test(): - f('string') + f('string')