PY-18254 IDEA-CR-8433 Represent types of varargs annotated with **T or *T as dicts and raw tuples

Type of vararg parameter should be seen differently inside
method body and at the call site. For instance, in case of positional
vararg, it should be represented as "Iterable[T]" in function quick
doc, but as "Tuple[T, ...]" inside its body, because without the latter
type checker might complain about missing tuple's methods called on
such parameter. Unfortunately, we're not able to represent homogeneous
tuples of arbitrary length in our type system so far, so it's just raw
tuple for now.

The same thing applies to keyword varargs, since collections.Mapping
doesn't contain all methods defined for built-in dict. At least we can
parametrize type of the standard dict properly with the types of its
keys and values.
This commit is contained in:
Mikhail Golubev
2016-02-15 17:45:44 +03:00
parent 7141f56ec2
commit 030853a3e2
5 changed files with 22 additions and 15 deletions
@@ -403,20 +403,20 @@ public class PyTypeParser {
return paramResult;
}
else if (starCount == 1) {
final PyType type = getTypeByName(anchor, PyNames.COLLECTIONS + "." + PyNames.ITERABLE);
if (type instanceof PyClassType) {
final PyClass pyClass = ((PyClassType)type).getPyClass();
final PyCollectionTypeImpl iterableType = new PyCollectionTypeImpl(pyClass, false, Collections.singletonList(paramType));
return paramResult.withType(iterableType);
final PyClassType tupleType = PyBuiltinCache.getInstance(anchor).getTupleType();;
// TODO How to represent unbound homogeneous tuple?
if (tupleType != null) {
return paramResult.withType(tupleType);
}
return EMPTY_RESULT;
}
else if (starCount == 2) {
final PyType type = getTypeByName(anchor, PyNames.COLLECTIONS + "." + PyNames.MAPPING);
if (type instanceof PyClassType) {
final PyClass pyClass = ((PyClassType)type).getPyClass();
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(anchor);
final PyClassType type = builtinCache.getDictType();
if (type != null) {
final PyClass pyClass = type.getPyClass();
return paramResult.withType(new PyCollectionTypeImpl(pyClass, false,
Arrays.asList(getTypeByName(anchor, "str"), paramType)));
Arrays.asList(builtinCache.getStrType(), paramType)));
}
return EMPTY_RESULT;
}
@@ -0,0 +1,4 @@
def f(*args, **kwargs):
# type: (*str, **str) -> None
args.index('foo')
kwargs.pop('bar')
@@ -291,15 +291,13 @@ public class PyTypeParserTest extends PyTestCase {
assertNotNull(type2);
assertEquals("MyObject", type2.getName());
final PyCollectionType type3 = as(parameterTypes.get(2).getType(context), PyCollectionType.class);
final PyClassType type3 = as(parameterTypes.get(2).getType(context), PyClassType.class);
assertNotNull(type3);
assertEquals("Iterable", type3.getName());
assertSize(1, type3.getElementTypes(context));
assertEquals("str", type3.getElementTypes(context).get(0).getName());
assertEquals("tuple", type3.getName());
final PyCollectionType type4 = as(parameterTypes.get(3).getType(context), PyCollectionType.class);
assertNotNull(type4);
assertEquals("Mapping", type4.getName());
assertEquals("dict", type4.getName());
assertSize(2, type4.getElementTypes(context));
assertEquals("str", type4.getElementTypes(context).get(0).getName());
assertEquals("int", type4.getElementTypes(context).get(1).getName());
@@ -451,7 +451,7 @@ public class PyTypingTest extends PyTestCase {
// PY-18254
public void testFunctionTypeComment() {
doTest("(x: int, args: Iterable[float], kwargs: Mapping[str, str]) -> List[bool]",
doTest("(x: int, args: tuple, kwargs: Dict[str, str]) -> List[bool]",
"from typing import List\n" +
"\n" +
"def f(x, *args, **kwargs):\n" +
@@ -537,6 +537,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-18254
public void testVarargsAnnotatedWithFunctionComment() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {