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.
This commit is contained in:
Andrey Vlasovskikh
2014-12-16 17:17:18 +03:00
parent 1fd35202a2
commit adc2d3859b
5 changed files with 40 additions and 5 deletions
@@ -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<String> expectedAttributes = ((PyStructuralType)expected).getAttributeNames();
final Set<String> actualAttributes = getAttributes(actual);
if (actualAttributes != null) {
final Sets.SetView<String> 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<String, String>() {
@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<String> 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()) {
@@ -205,7 +205,7 @@ public class PyTypeChecker {
}
@NotNull
private static Set<String> getClassAttributes(@NotNull PyClass cls, boolean inherited) {
public static Set<String> getClassAttributes(@NotNull PyClass cls, boolean inherited) {
final Set<String> attributes = new HashSet<String>();
for (PyFunction function : cls.getMethods(false)) {
attributes.add(function.getName());
@@ -12,5 +12,5 @@ def test(x):
g(x)
z = 'foo'
f(<warning descr="Expected type '{foo, bar}', got 'str' instead">z</warning>)
f(<warning descr="Type 'str' doesn't have expected attributes 'foo', 'bar'">z</warning>)
g(z)
@@ -18,4 +18,4 @@ class E(object):
f(C())
f(D())
f(<warning descr="Expected type '{foo}', got 'E' instead">E()</warning>)
f(<warning descr="Type 'E' doesn't have expected attribute 'foo'">E()</warning>)
@@ -7,4 +7,4 @@ def g(x):
def test():
f(<warning descr="Expected type '{foo, bar}', got 'str' instead">'string'</warning>)
f(<warning descr="Type 'str' doesn't have expected attributes 'foo', 'bar'">'string'</warning>)