Provide special type for typing.TypeVar as a callee (PY-28127)

This commit is contained in:
Semyon Proshev
2018-04-27 19:57:14 +03:00
parent 92073b921c
commit c1abea5187
8 changed files with 125 additions and 10 deletions
@@ -86,9 +86,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
private static final String CHAIN_MAP = "typing.ChainMap";
private static final String UNION = "typing.Union";
private static final String OPTIONAL = "typing.Optional";
private static final String CLASSVAR = "typing.ClassVar";
public static final String NAMEDTUPLE_SIMPLE = "NamedTuple";
private static final String PY2_FILE_TYPE = "typing.BinaryIO";
private static final String PY3_BINARY_FILE_TYPE = "typing.BinaryIO";
@@ -176,7 +173,12 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
}
}
return getNewTypeForReference(referenceExpression, context);
final PyType newType = getNewTypeForReference(referenceExpression, context);
if (newType != null) {
return newType;
}
return getTypeVarTypeForCallee(referenceExpression, context);
}
@Override
@@ -295,6 +297,32 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
return new PyCustomType(CALLABLE, null, false, true, PyBuiltinCache.getInstance(anchor).getObjectType());
}
@Nullable
private static PyType getTypeVarTypeForCallee(@NotNull PyReferenceExpression referenceExpression, @NotNull TypeEvalContext context) {
if (PyCallExpressionNavigator.getPyCallExpressionByCallee(referenceExpression) == null) return null;
if (resolveToQualifiedNames(referenceExpression, context).contains(TYPE_VAR)) {
final List<PyCallableParameter> parameters = new ArrayList<>();
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(referenceExpression);
final LanguageLevel languageLevel = LanguageLevel.forElement(referenceExpression);
final PyElementGenerator generator = PyElementGenerator.getInstance(referenceExpression.getProject());
parameters.add(PyCallableParameterImpl.nonPsi("name", builtinCache.getStringType(languageLevel)));
parameters.add(PyCallableParameterImpl.positionalNonPsi("constraints", builtinCache.getTypeType()));
parameters.add(PyCallableParameterImpl.nonPsi("bound", builtinCache.getTypeType(), generator.createEllipsis()));
final PyClassType boolType = builtinCache.getBoolType();
final PyExpression falseValue = generator.createExpressionFromText(languageLevel, "False");
parameters.add(PyCallableParameterImpl.nonPsi("covariant", boolType, falseValue));
parameters.add(PyCallableParameterImpl.nonPsi("contravariant", boolType, falseValue));
return new PyCallableTypeImpl(parameters, null);
}
return null;
}
private static boolean omitFirstParamInTypeComment(@NotNull PyFunction func, @NotNull PyFunctionTypeAnnotation annotation) {
return func.getContainingClass() != null && func.getModifier() != PyFunction.Modifier.STATICMETHOD &&
annotation.getParameterTypeList().getParameterTypes().size() < func.getParameterList().getParameters().length;
@@ -35,15 +35,21 @@ public class PyCallableParameterImpl implements PyCallableParameter {
@Nullable private final Ref<PyType> myType;
@Nullable private final PyExpression myDefaultValue;
@Nullable private final PyParameter myElement;
private final boolean myIsPositional;
private final boolean myIsKeyword;
private PyCallableParameterImpl(@Nullable String name,
@Nullable Ref<PyType> type,
@Nullable PyExpression defaultValue,
@Nullable PyParameter element) {
@Nullable PyParameter element,
boolean isPositional,
boolean isKeyword) {
myName = name;
myType = type;
myDefaultValue = defaultValue;
myElement = element;
myIsPositional = isPositional;
myIsKeyword = isKeyword;
}
@NotNull
@@ -58,17 +64,27 @@ public class PyCallableParameterImpl implements PyCallableParameter {
@NotNull
public static PyCallableParameter nonPsi(@Nullable String name, @Nullable PyType type, @Nullable PyExpression defaultValue) {
return new PyCallableParameterImpl(name, Ref.create(type), defaultValue, null);
return new PyCallableParameterImpl(name, Ref.create(type), defaultValue, null, false, false);
}
@NotNull
public static PyCallableParameter positionalNonPsi(@Nullable String name, @Nullable PyType type) {
return new PyCallableParameterImpl(name, Ref.create(type), null, null, true, false);
}
@NotNull
public static PyCallableParameter keywordNonPsi(@Nullable String name, @Nullable PyType type) {
return new PyCallableParameterImpl(name, Ref.create(type), null, null, false, true);
}
@NotNull
public static PyCallableParameter psi(@NotNull PyParameter parameter) {
return new PyCallableParameterImpl(null, null, null, parameter);
return new PyCallableParameterImpl(null, null, null, parameter, false, false);
}
@NotNull
public static PyCallableParameter psi(@NotNull PyParameter parameter, @Nullable PyType type) {
return new PyCallableParameterImpl(null, Ref.create(type), null, parameter);
return new PyCallableParameterImpl(null, Ref.create(type), null, parameter, false, false);
}
@Nullable
@@ -121,12 +137,16 @@ public class PyCallableParameterImpl implements PyCallableParameter {
@Override
public boolean isPositionalContainer() {
if (myIsPositional) return true;
final PyNamedParameter namedParameter = PyUtil.as(myElement, PyNamedParameter.class);
return namedParameter != null && namedParameter.isPositionalContainer();
}
@Override
public boolean isKeywordContainer() {
if (myIsKeyword) return true;
final PyNamedParameter namedParameter = PyUtil.as(myElement, PyNamedParameter.class);
return namedParameter != null && namedParameter.isKeywordContainer();
}
@@ -210,13 +230,16 @@ public class PyCallableParameterImpl implements PyCallableParameter {
if (o == null || getClass() != o.getClass()) return false;
final PyCallableParameterImpl parameter = (PyCallableParameterImpl)o;
return Objects.equals(myName, parameter.myName) &&
return myIsPositional == parameter.myIsPositional &&
myIsKeyword == parameter.myIsKeyword &&
Objects.equals(myName, parameter.myName) &&
Objects.equals(Ref.deref(myType), Ref.deref(parameter.myType)) &&
Objects.equals(myDefaultValue, parameter.myDefaultValue) &&
Objects.equals(myElement, parameter.myElement);
}
@Override
public int hashCode() {
return Objects.hash(myName, Ref.deref(myType), myElement);
return Objects.hash(myName, Ref.deref(myType), myDefaultValue, myElement, myIsPositional, myIsKeyword);
}
}
@@ -0,0 +1,33 @@
from typing import TypeVar
TypeVar("T")
TypeVar("T", int)
TypeVar("T", int, str)
TypeVar("T", bound=int)
TypeVar("T", int, bound=int)
TypeVar("T", int, str, bound=int)
TypeVar("T", <warning descr="Unexpected argument">bd=int</warning>)
TypeVar("T", int, <warning descr="Unexpected argument">bd=int</warning>)
TypeVar("T", int, str, <warning descr="Unexpected argument">bd=int</warning>)
TypeVar("T", bound=int, covariant=True)
TypeVar("T", int, bound=int, covariant=True)
TypeVar("T", int, str, bound=int, covariant=True)
TypeVar("T", bound=int, <warning descr="Unexpected argument">cant=True</warning>)
TypeVar("T", int, bound=int, <warning descr="Unexpected argument">cant=True</warning>)
TypeVar("T", int, str, bound=int, <warning descr="Unexpected argument">cant=True</warning>)
TypeVar("T", bound=int, covariant=True, contravariant=True)
TypeVar("T", int, bound=int, covariant=True, contravariant=True)
TypeVar("T", int, str, bound=int, covariant=True, contravariant=True)
TypeVar("T", bound=int, covariant=True, <warning descr="Unexpected argument">cant=True</warning>)
TypeVar("T", int, bound=int, covariant=True, <warning descr="Unexpected argument">cant=True</warning>)
TypeVar("T", int, str, bound=int, covariant=True, <warning descr="Unexpected argument">cant=True</warning>)
TypeVar("T", bound=int, covariant=True, contravariant=True, <warning descr="Unexpected argument">more=5</warning>)
TypeVar("T", int, bound=int, covariant=True, contravariant=True, <warning descr="Unexpected argument">more=5</warning>)
TypeVar("T", int, str, bound=int, covariant=True, contravariant=True, <warning descr="Unexpected argument">more=5</warning>)
@@ -0,0 +1,4 @@
from typing import TypeVar
TypeVar("T", int, str, bound=int, covariant=True, contravariant=True)
TypeVar("T", <warning descr="Expected type 'type', got 'int' instead">0</warning>, <warning descr="Expected type 'type', got 'int' instead">1</warning>, <warning descr="Expected type 'type', got 'int' instead">bound=2</warning>, <warning descr="Expected type 'bool', got 'int' instead">covariant=3</warning>, <warning descr="Expected type 'bool', got 'int' instead">contravariant=4</warning>)
@@ -0,0 +1,3 @@
from typing import TypeVar
T = TypeVar(<arg1>)
@@ -782,6 +782,20 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
);
}
// PY-28127
public void testInitializingTypeVar() {
runWithLanguageLevel(
LanguageLevel.PYTHON34,
() -> {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
feignCtrlP(offset).check("name: str, *constraints: type, bound: type=..., covariant: bool=False, contravariant: bool=False",
new String[]{"name: str, "},
ArrayUtil.EMPTY_STRING_ARRAY);
}
);
}
/**
* Imitates pressing of Ctrl+P; fails if results are not as expected.
* @param offset offset of 'cursor' where Ctrl+P is pressed.
@@ -301,4 +301,9 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
public void testDataclassesReplace() {
runWithLanguageLevel(LanguageLevel.PYTHON37, () -> super.doMultiFileTest());
}
// PY-28127
public void testInitializingTypeVar() {
doTest();
}
}
@@ -347,4 +347,9 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
public void testObjectMethodInPossiblyInheritanceChain() {
doTest();
}
// PY-28127
public void testInitializingTypeVar() {
doTest();
}
}