mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Added bounded generic types inference and checking
This commit is contained in:
@@ -93,20 +93,19 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
final String superName = PythonDocumentationProvider.getTypeName(superType, context);
|
||||
String expected = String.format("'%s'", superName);
|
||||
final boolean hasGenerics = PyTypeChecker.hasGenerics(superType, context);
|
||||
ProblemHighlightType highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
if (hasGenerics) {
|
||||
final PyType subst = PyTypeChecker.substitute(superType, substitutions, context);
|
||||
if (subst != null) {
|
||||
expected = String.format("'%s' (matched generic type '%s')",
|
||||
PythonDocumentationProvider.getTypeName(subst, context),
|
||||
superName);
|
||||
|
||||
highlightType = ProblemHighlightType.WEAK_WARNING;
|
||||
}
|
||||
}
|
||||
final String msg = String.format("Expected type %s, got '%s' instead",
|
||||
expected,
|
||||
PythonDocumentationProvider.getTypeName(subType, context));
|
||||
final ProblemHighlightType highlightType = hasGenerics ? ProblemHighlightType.WEAK_WARNING :
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
registerProblem(node, msg, highlightType);
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PyGenericType implements PyType {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return myName;
|
||||
return myBound != null ? myName + " (" + myBound.getName() + ")" : myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,7 +71,7 @@ public class PyGenericType implements PyType {
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PyGenericType: " + myName;
|
||||
return "PyGenericType: " + getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -44,6 +44,32 @@ public class PyTypeChecker {
|
||||
private static boolean match(@Nullable PyType expected, @Nullable PyType actual, @NotNull TypeEvalContext context,
|
||||
@Nullable Map<PyGenericType, PyType> substitutions, boolean recursive) {
|
||||
// TODO: subscriptable types?, module types?, etc.
|
||||
if (expected instanceof PyGenericType && substitutions != null) {
|
||||
final PyGenericType generic = (PyGenericType)expected;
|
||||
final PyType subst = substitutions.get(generic);
|
||||
final PyType bound = generic.getBound();
|
||||
if (!match(bound, actual, context, substitutions, recursive)) {
|
||||
return false;
|
||||
}
|
||||
else if (subst != null) {
|
||||
if (expected.equals(actual)) {
|
||||
return true;
|
||||
}
|
||||
else if (recursive) {
|
||||
return match(subst, actual, context, substitutions, false);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (actual != null && !(actual instanceof PyReturnTypeReference)) {
|
||||
substitutions.put(generic, actual);
|
||||
}
|
||||
else if (bound != null) {
|
||||
substitutions.put(generic, bound);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (expected == null || actual == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -62,25 +88,6 @@ public class PyTypeChecker {
|
||||
if (actual instanceof PyTypeReference) {
|
||||
return match(expected, ((PyTypeReference)actual).resolve(null, context), context, substitutions, false);
|
||||
}
|
||||
if (expected instanceof PyGenericType && substitutions != null) {
|
||||
final PyGenericType generic = (PyGenericType)expected;
|
||||
final PyType subst = substitutions.get(generic);
|
||||
if (subst != null) {
|
||||
if (expected.equals(actual)) {
|
||||
return true;
|
||||
}
|
||||
else if (recursive) {
|
||||
return match(subst, actual, context, substitutions, false);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
substitutions.put(generic, actual);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (isUnknown(actual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -215,4 +215,9 @@ public class PyUnionType implements PyType {
|
||||
public int hashCode() {
|
||||
return myMembers.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PyUnionType: " + getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
def test():
|
||||
def f(x):
|
||||
"""
|
||||
:type x: T (int or str)
|
||||
:rtype: T
|
||||
"""
|
||||
pass
|
||||
|
||||
x = f(10)
|
||||
y = f('foo')
|
||||
z = f(<warning descr="Expected type 'T (one of (int, str))', got 'list' instead">[]</warning>)
|
||||
return x + <warning descr="Expected type 'one of (int, long, float, complex)', got 'str' instead">y</warning>
|
||||
@@ -572,6 +572,17 @@ public class PyTypeTest extends PyTestCase {
|
||||
" foo(3)\n");
|
||||
}
|
||||
|
||||
public void testUpperBoundGeneric() {
|
||||
doTest("int or str",
|
||||
"def foo(x):\n" +
|
||||
" '''\n" +
|
||||
" :type x: T (int or str)\n" +
|
||||
" :rtype: T\n" +
|
||||
" '''\n" +
|
||||
"def bar(x):\n" +
|
||||
" expr = foo(x)\n");
|
||||
}
|
||||
|
||||
private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) {
|
||||
return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing();
|
||||
}
|
||||
@@ -594,6 +605,7 @@ public class PyTypeTest extends PyTestCase {
|
||||
PyType expected = PyTypeParser.getTypeByName(expr, expectedType);
|
||||
if (expected != null) {
|
||||
assertNotNull(context.printTrace(), actual);
|
||||
assertFalse(context.printTrace(), actual instanceof PyReturnTypeReference);
|
||||
assertTrue(msg(expected, actual, context), PyTypeChecker.match(expected, actual, context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,4 +180,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
|
||||
public void testFieldWithNoneInStub() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testBoundedGeneric() {
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user