PY-9662 Fixed: Type of expression * int literal results is detected as int while it's unknown

Update PyBinaryExpressionImpl to return weak type if one of operands is unknown
This commit is contained in:
Semyon Proshev
2017-08-18 14:50:25 +03:00
parent 5def6b554f
commit 7b1403831a
5 changed files with 66 additions and 15 deletions
@@ -159,15 +159,9 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
boolean matched = true;
for (Map.Entry<PyExpression, PyCallableParameter> entry : result.getMappedParameters().entrySet()) {
final PyExpression argument = entry.getKey();
final PyCallableParameter parameter = entry.getValue();
if (parameter.isPositionalContainer() || parameter.isKeywordContainer()) {
continue;
}
final Map<PyGenericType, PyType> substitutions = new HashMap<>();
final PyType parameterType = parameter.getType(context);
final PyType argumentType = context.getType(argument);
if (!PyTypeChecker.match(parameterType, argumentType, context, substitutions)) {
final PyType parameterType = entry.getValue().getArgumentType(context);
final PyType argumentType = context.getType(entry.getKey());
if (!PyTypeChecker.match(parameterType, argumentType, context, new HashMap<>())) {
matched = false;
}
}
@@ -179,11 +173,11 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
}
}
}
if (!matchedTypes.isEmpty()) {
return PyUnionType.union(matchedTypes);
}
if (!types.isEmpty()) {
return PyUnionType.union(types);
final boolean bothOperandsAreKnown = operandIsKnown(getLeftExpression(), context) && operandIsKnown(getRightExpression(), context);
final List<PyType> resultTypes = !matchedTypes.isEmpty() ? matchedTypes : types;
if (!resultTypes.isEmpty()) {
final PyType result = PyUnionType.union(resultTypes);
return bothOperandsAreKnown ? result : PyUnionType.createWeakType(result);
}
}
if (PyNames.COMPARISON_OPERATORS.contains(getReferencedName())) {
@@ -223,6 +217,15 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
return op != null ? op.getNode() : null;
}
private static boolean operandIsKnown(@Nullable PyExpression operand, @NotNull TypeEvalContext context) {
if (operand == null) return false;
final PyType operandType = context.getType(operand);
if (operandType instanceof PyStructuralType || PyTypeChecker.isUnknown(operandType, context)) return false;
return true;
}
private static boolean isTrueDivEnabled(@NotNull PyElement anchor) {
final PsiFile file = anchor.getContainingFile();
if (file instanceof PyFile) {
@@ -0,0 +1,17 @@
from typing import Any
def f(x: Any) -> str:
return x * 2
def f(x: Any) -> str:
return 2 * x
def f(x) -> str:
return x * 2
def f(x) -> str:
return 2 * x
@@ -1,5 +1,5 @@
def test():
xs = map(lambda x: x + 1, [1, 2, 3])
print('foo' + <warning descr="Expected type 'TypeVar('AnyStr', str, unicode)', got 'int' instead">xs[0]</warning>)
print('foo' + xs[0])
ys = map(tuple, iter([1, 2, 3]))
print(1 + <warning descr="Expected type 'int', got 'tuple' instead">ys[0]</warning>, 'bar' + <warning descr="Expected type 'TypeVar('AnyStr', str, unicode)', got 'tuple' instead">ys[1]</warning>)
@@ -2237,6 +2237,32 @@ public class PyTypeTest extends PyTestCase {
);
}
// PY-9662
public void testBinaryExpressionWithUnknownOperand() {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
doTest("Union[int, Any]",
"from typing import Any\n" +
"x: Any\n" +
"expr = x * 2");
doTest("Union[int, Any]",
"from typing import Any\n" +
"x: Any\n" +
"expr = 2 * x");
doTest("Union[int, Any]",
"def f(x):\n" +
" expr = x * 2");
doTest("Union[int, Any]",
"def f(x):\n" +
" expr = 2 * x");
}
);
}
private static List<TypeEvalContext> getTypeEvalContexts(@NotNull PyExpression element) {
return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(),
TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());
@@ -442,6 +442,11 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
doTest();
}
// PY-9662
public void testBinaryExpressionWithUnknownOperand() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
}
// PY-16066
public void testBasestringMatchesType() {
doTest();