diff --git a/mypy/applytype.py b/mypy/applytype.py index 75831116562e..e4ebede3310e 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -1,9 +1,12 @@ -from typing import List, Dict +from typing import List, Dict, Sequence, Tuple import mypy.subtypes from mypy.sametypes import is_same_type from mypy.expandtype import expand_type -from mypy.types import Type, TypeVarId, TypeVarType, CallableType, AnyType, PartialType +from mypy.types import ( + Type, TypeVarId, TypeVarType, TypeVisitor, CallableType, AnyType, PartialType, + Instance, UnionType +) from mypy.messages import MessageBuilder from mypy.nodes import Context @@ -38,7 +41,13 @@ def apply_generic_arguments(callable: CallableType, types: List[Type], types[i] = value break else: - msg.incompatible_typevar_value(callable, type, callable.variables[i].name, context) + constraints = get_inferred_object_constraints(msg, callable.arg_types, type, i + 1) + if constraints: + msg.incompatible_inferred_object_arguments( + callable, i + 1, constraints, context) + else: + msg.incompatible_typevar_value( + callable, type, callable.variables[i].name, context) upper_bound = callable.variables[i].upper_bound if (type and not isinstance(type, PartialType) and not mypy.subtypes.is_subtype(type, upper_bound)): @@ -61,3 +70,38 @@ def apply_generic_arguments(callable: CallableType, types: List[Type], ret_type=expand_type(callable.ret_type, id_to_type), variables=remaining_tvars, ) + + +def get_inferred_object_constraints(msg: MessageBuilder, + arg_types: Sequence[Type], + type: Type, + index: int) -> Dict[str, Tuple[str, ...]]: + """Gets incompatible function arguments that are inferred as object based on the type + constraints. + + An example of a constrained type is AnyStr which must be all str or all byte. When there is a + mismatch of arguments with a constrained type like AnyStr, then the inferred type is object. + """ + constraints = {} # type: Dict[str, Tuple[str, ...]] + if isinstance(type, Instance) and type.type.fullname() == 'builtins.object': + if index == len(arg_types): + # Index is off by one for '*' arguments + constraints = add_inferred_object_arg_constraints( + msg, constraints, arg_types[index - 1]) + else: + constraints = add_inferred_object_arg_constraints(msg, constraints, arg_types[index]) + return constraints + + +def add_inferred_object_arg_constraints(msg: MessageBuilder, + constraints: Dict[str, Tuple[str, ...]], + arg_type: Type) -> Dict[str, Tuple[str, ...]]: + if (isinstance(arg_type, TypeVarType) and + arg_type.values and + len(arg_type.values) > 1 and + arg_type.name not in constraints.keys()): + constraints[arg_type.name] = tuple(msg.format(val) for val in arg_type.values) + elif isinstance(arg_type, UnionType): + for item in arg_type.items: + constraints = add_inferred_object_arg_constraints(msg, constraints, item) + return constraints diff --git a/mypy/messages.py b/mypy/messages.py index 995cdcdb707f..c921b565f598 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union, Mapping from mypy.erasetype import erase_type from mypy.errors import Errors @@ -869,6 +869,21 @@ def incompatible_typevar_value(self, self.fail(INCOMPATIBLE_TYPEVAR_VALUE.format(typevar_name, callable_name(callee), self.format(typ)), context) + def incompatible_inferred_object_arguments(self, + callee: CallableType, + index: int, + constraints: Mapping[str, Sequence[str]], + context: Context) -> None: + for key, values in constraints.items(): + self.fail('Argument {} of {} has incompatible value'.format( + index, callable_name(callee)), context) + if len(values) == 2: + constraint_str = '{} or {}'.format(values[0], values[1]) + elif len(values) > 3: + constraint_str = ', '.join(values[:-1]) + ', or ' + values[-1] + self.note('"{}" must be all one type: {}'.format( + key, constraint_str), context) + def overloaded_signatures_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} overlap with ' diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 04bb746ad66b..0f043debdf5c 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2162,3 +2162,72 @@ def i() -> List[Union[str, int]]: return x [builtins fixtures/dict.pyi] + +[case testAnyStrIncompatibleArguments] +from typing import TypeVar +AnyStr = TypeVar('AnyStr', str, bytes) +def f(x: AnyStr, y: AnyStr) -> None: pass +def g(x: AnyStr, y: AnyStr, z: int) -> AnyStr: pass +f('a', 'b') +f(b'a', b'b') +f('a', b'b') # E: Argument 1 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" +g('a', 'b', 1) +g(b'a', b'b', 1) +g('a', b'b', 1) # E: Argument 1 of "g" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" +g('a', b'b', 'c') # E: Argument 1 of "g" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" \ +# E: Argument 3 to "g" has incompatible type "str"; expected "int" + +[case testUnionAnyStrIncompatibleArguments] +from typing import TypeVar, Union +AnyStr = TypeVar('AnyStr', str, bytes) +def f(x: Union[AnyStr, int], y: AnyStr) -> None: pass +f('a', 'b') +f(1, 'b') +f('a', b'b') # E: Argument 1 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" + +[case testStarAnyStrIncompatibleArguments] +from typing import TypeVar, Union +AnyStr = TypeVar('AnyStr', str, bytes) +def f(*x: AnyStr) -> None: pass +def g(x: int, *y: AnyStr) -> None: pass +def h(*x: AnyStr, y: int) -> None: pass +f('a') +f('a', 'b') +f('a', b'b') # E: Argument 1 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" +g(1, 'a') +g(1, 'a', b'b') # E: Argument 1 of "g" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" +h('a', y=1) +h('a', 'b', y=1) +h('a', b'b', y=1) # E: Value of type variable "AnyStr" of "h" cannot be "object" + +[case testConstrainedIncompatibleArguments] +from typing import TypeVar +S = TypeVar('S', int, str) +def f(x: S, y: S) -> S: return (x + y) +f('1', '2') +f('1', 2) # E: Argument 1 of "f" has incompatible value \ +# N: "S" must be all one type: "int" or "str" + +[case testMultipleConstrainedIncompatibleArguments] +from typing import TypeVar +S = TypeVar('S', int, str) +AnyStr = TypeVar('AnyStr', str, bytes) +def f(a: S, b: S, c: AnyStr, d: AnyStr) -> S: return (a + b) +f('1', '2', '3', '4') +f('1', '2', b'3', b'4') +f(1, 2, '3', '4') +f(1, 2, b'3', b'4') +f(1, '2', '3', '4') # E: Argument 1 of "f" has incompatible value \ +# N: "S" must be all one type: "int" or "str" +f('1', '2', b'3', '4') # E: Argument 2 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" +f('1', 2, b'3', '4') # E: Argument 1 of "f" has incompatible value \ +# N: "S" must be all one type: "int" or "str" \ +# E: Argument 2 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "str" or "bytes" diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 41d8dbe5441f..497b63b598e8 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -750,10 +750,12 @@ AnyStr = TypeVar('AnyStr', bytes, str) def f(x: Union[AnyStr, int], *a: AnyStr) -> None: pass f('foo') f('foo', 'bar') -f('foo', b'bar') # E: Value of type variable "AnyStr" of "f" cannot be "object" +f('foo', b'bar') # E: Argument 1 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "bytes" or "str" f(1) f(1, 'foo') -f(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "f" cannot be "object" +f(1, 'foo', b'bar') # E: Argument 1 of "f" has incompatible value \ +# N: "AnyStr" must be all one type: "bytes" or "str" [builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 0f5060fe3a12..b6acf3c33a9c 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -998,10 +998,12 @@ def g(x: int, *a: AnyStr) -> None: pass g('foo') g('foo', 'bar') -g('foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "object" +g('foo', b'bar') # E: Argument 1 of "g" has incompatible value \ +# N: "AnyStr" must be all one type: "bytes" or "str" g(1) g(1, 'foo') -g(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "object" +g(1, 'foo', b'bar') # E: Argument 1 of "g" has incompatible value \ +# N: "AnyStr" must be all one type: "bytes" or "str" [builtins fixtures/primitives.pyi] [case testBadOverlapWithTypeVarsWithValues] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index dd73dbb18a90..b7bca30befe2 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -7,7 +7,9 @@ T = TypeVar('T', int, str) def f(x: T) -> None: pass f(1) f('x') -f(object()) # E: Value of type variable "T" of "f" cannot be "object" +f(object()) # E: Argument 1 of "f" has incompatible value \ +# N: "T" must be all one type: "int" or "str" + [case testCallGenericFunctionWithTypeVarValueRestrictionUsingContext] from typing import TypeVar, List @@ -18,7 +20,8 @@ s = ['x'] o = [object()] i = f(1) s = f('') -o = f(1) # E: Value of type variable "T" of "f" cannot be "object" +o = f(1) # E: Argument 1 of "f" has incompatible value \ +# N: "T" must be all one type: "int" or "str" [builtins fixtures/list.pyi] [case testCallGenericFunctionWithTypeVarValueRestrictionAndAnyArgs] @@ -239,7 +242,8 @@ class A(Generic[X]): A(1) A('x') A(cast(Any, object())) -A(object()) # E: Value of type variable "X" of "A" cannot be "object" +A(object()) # E: Argument 1 of "A" has incompatible value \ +# N: "X" must be all one type: "int" or "str" [case testGenericTypeWithTypevarValuesAndTypevarArgument] from typing import TypeVar, Generic diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 68af402e4601..06633ad1629d 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1277,7 +1277,8 @@ re.subn(bpat, b'', b'')[0] + b'' re.subn(bre, lambda m: b'', b'')[0] + b'' re.subn(bpat, lambda m: b'', b'')[0] + b'' [out] -_program.py:7: error: Value of type variable "AnyStr" of "search" cannot be "object" +_program.py:7: error: Argument 1 of "search" has incompatible value +_program.py:7: note: "AnyStr" must be all one type: "str" or "bytes" _program.py:9: error: Cannot infer type argument 1 of "search" [case testReModuleString] @@ -1301,7 +1302,8 @@ re.subn(spat, '', '')[0] + '' re.subn(sre, lambda m: '', '')[0] + '' re.subn(spat, lambda m: '', '')[0] + '' [out] -_program.py:7: error: Value of type variable "AnyStr" of "search" cannot be "object" +_program.py:7: error: Argument 1 of "search" has incompatible value +_program.py:7: note: "AnyStr" must be all one type: "str" or "bytes" _program.py:9: error: Cannot infer type argument 1 of "search" [case testListSetitemTuple]