From 4f6d386c31f5edaec0c93c95d2b655e7370a5b92 Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 13:30:51 -0700 Subject: [PATCH 01/30] Add notes to invariant argument error --- mypy/messages.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mypy/messages.py b/mypy/messages.py index 5c2f5d16fdc7..8a5eedcd3093 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -520,6 +520,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: target = 'to {} '.format(name) msg = '' + hints = [] if callee.name == '': name = callee.name[1:-1] n -= 1 @@ -558,7 +559,11 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: arg_type_str = '**' + arg_type_str msg = 'Argument {} {}has incompatible type {}; expected {}'.format( n, target, arg_type_str, expected_type_str) + hints = invariance_hints(hints, arg_type, expected_type) self.fail(msg, context) + if hints: + for hint in hints: + self.note(hint, context) def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str, context: Context) -> None: @@ -977,3 +982,16 @@ def pretty_or(args: List[str]) -> str: if len(quoted) == 2: return "{} or {}".format(quoted[0], quoted[1]) return ", ".join(quoted[:-1]) + ", or " + quoted[-1] + + +def invariance_hints(hints: List[str], arg_type, expected_type) -> List[str]: + '''Explain that the type is invariant and give hints for how to solve the issue.''' + if expected_type.type.name() == 'list' and arg_type.type.name() == 'list': + hints.append('"List" is invariant --- see https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance') + hints.append('Consider using "Sequence" instead, which is covariant') + elif (expected_type.type.name() == 'dict' and arg_type.type.name() == 'dict' and + expected_type.args[0].type == arg_type.args[0].type and + expected_type.args[1].type != arg_type.args[1].type): + hints.append('The second type parameter of "Dict" is invariant --- see ') + hints.append('Consider using "Mapping" instead, which has covariant type parameters') + return hints From c12fc40e597132675c4a9a974410aa748bd5de30 Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 15:40:37 -0700 Subject: [PATCH 02/30] Fix type inference --- mypy/messages.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 8a5eedcd3093..1979a8ece2c2 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -520,7 +520,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: target = 'to {} '.format(name) msg = '' - hints = [] + notes = [] # type: List[str] if callee.name == '': name = callee.name[1:-1] n -= 1 @@ -559,11 +559,11 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: arg_type_str = '**' + arg_type_str msg = 'Argument {} {}has incompatible type {}; expected {}'.format( n, target, arg_type_str, expected_type_str) - hints = invariance_hints(hints, arg_type, expected_type) + notes = invariance_notes(notes, arg_type_str, expected_type_str) self.fail(msg, context) - if hints: - for hint in hints: - self.note(hint, context) + if notes: + for note_msg in notes: + self.note(note_msg, context) def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str, context: Context) -> None: @@ -984,14 +984,22 @@ def pretty_or(args: List[str]) -> str: return ", ".join(quoted[:-1]) + ", or " + quoted[-1] -def invariance_hints(hints: List[str], arg_type, expected_type) -> List[str]: - '''Explain that the type is invariant and give hints for how to solve the issue.''' - if expected_type.type.name() == 'list' and arg_type.type.name() == 'list': - hints.append('"List" is invariant --- see https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance') - hints.append('Consider using "Sequence" instead, which is covariant') - elif (expected_type.type.name() == 'dict' and arg_type.type.name() == 'dict' and - expected_type.args[0].type == arg_type.args[0].type and - expected_type.args[1].type != arg_type.args[1].type): - hints.append('The second type parameter of "Dict" is invariant --- see ') - hints.append('Consider using "Mapping" instead, which has covariant type parameters') - return hints +def invariance_notes(notes: List[str], arg_type: str, expected_type: str) -> List[str]: + '''Explain that the type is invariant and give notes for how to solve the issue.''' + if expected_type.startswith('List') and arg_type.startswith('List'): + notes = append_invariance_message(notes, 'List', 'Sequence') + elif (expected_type.startswith('Dict') and arg_type.startswith('Dict') and + expected_type.split(',')[0] == arg_type.split(',')[0] and + expected_type.split(',')[1:] != arg_type.split(',')[1:]): + notes = append_invariance_message(notes, 'Dict', 'Mapping') + return notes + + +def append_invariance_message(notes: List[str], + invariant_type: str, + suggested_alternative: str) -> List[str]: + notes.append( + '"' + invariant_type + '" is invariant --- see ' + + 'http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance') + notes.append('Consider using "' + suggested_alternative + '" instead, which is covariant') + return notes From 2b92465badbe96ef3ea1491a17aafdf6aa24e17f Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 16:07:39 -0700 Subject: [PATCH 03/30] Add invariant notes to tests --- test-data/unit/check-inference.test | 27 +++++++++++++++++++-------- test-data/unit/check-lists.test | 11 +++++++++++ test-data/unit/check-overloading.test | 12 +++++++++--- test-data/unit/check-varargs.test | 12 ++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 42cd312c0531..4f0010bba688 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -669,14 +669,14 @@ from typing import TypeVar, Union, List T = TypeVar('T') U = TypeVar('U') def f(x: Union[T, int], y: T) -> T: pass -f(1, 'a')() # E: "str" not callable -f('a', 1)() # E: "object" not callable -f('a', 'a')() # E: "str" not callable -f(1, 1)() # E: "int" not callable +f(1, 'a')() +f('a', 1)() +f('a', 'a')() +f(1, 1)() def g(x: Union[T, List[T]]) -> List[T]: pass def h(x: List[str]) -> None: pass -g('a')() # E: List[str] not callable +g('a')() # The next line is a case where there are multiple ways to satisfy a constraint # involving a Union. Either T = List[str] or T = str would turn out to be valid, @@ -684,8 +684,7 @@ g('a')() # E: List[str] not callable # to backtrack later) and defaults to T = . The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] - +g(['a']) h(g(['a'])) def i(x: Union[List[T], List[U]], y: List[T], z: List[U]) -> None: pass @@ -693,7 +692,19 @@ a = [1] b = ['b'] i(a, a, b) i(b, a, b) -i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] +i(a, b, b) # error: Argument 1 to "i" has incompatible type List[int]; expected List[str] +[out] +main:5: error: "str" not callable +main:6: error: "object" not callable +main:7: error: "str" not callable +main:8: error: "int" not callable +main:12: error: List[str] not callable +main:20: error: Argument 1 to "g" has incompatible type List[str]; expected List[] +main:20: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance +main:20: note: Consider using "Sequence" instead, which is covariant +main:28: error: Argument 1 to "i" has incompatible type List[int]; expected List[str] +main:28: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance +main:28: note: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testCallableListJoinInference] diff --git a/test-data/unit/check-lists.test b/test-data/unit/check-lists.test index c9c67e80d4fb..7bb0c8affdbe 100644 --- a/test-data/unit/check-lists.test +++ b/test-data/unit/check-lists.test @@ -70,3 +70,14 @@ reveal_type(b) # E: Revealed type is 'builtins.list[builtins.int*]' c = [*a, 0] reveal_type(c) # E: Revealed type is 'builtins.list[builtins.int*]' [builtins fixtures/list.pyi] + +[case testListInvarianceNote] +from typing import List, Union +def f(numbers: List[Union[int, float]]) -> None: pass +a = [1, 2] +f(a) +[out] +main:4: error: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] +main:4: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance +main:4: note: Consider using "Sequence" instead, which is covariant +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 69289fae18c1..b8ddf0101c43 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -608,9 +608,15 @@ n = f(list_int) m = f(list_str) n = 1 m = 1 -n = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -m = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] +n = 'x' +m = 'x' +f(list_object) +[out] +tmp/foo.pyi:13: error: Incompatible types in assignment (expression has type "str", variable has type "int") +tmp/foo.pyi:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") +tmp/foo.pyi:15: error: Argument 1 to "f" has incompatible type List[object]; expected List[int] +tmp/foo.pyi:15: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance +tmp/foo.pyi:15: note: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 566841888693..f5b1dfbeadf1 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -592,3 +592,15 @@ class C: def foo(self) -> None: pass C().foo() C().foo(1) # The decorator's return type says this should be okay + + +[case testInvariantDictNote] +from typing import Dict, Sequence +def f(a: Dict[str, Sequence[int]]) -> None: pass +a = {'a': [1, 2]} +f(a) +[out] +main:4: error: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] +main:4: note: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance +main:4: note: Consider using "Mapping" instead, which is covariant +[builtins fixtures/dict.pyi] From 05a8ee8ae7c1c7c8a31dbda7257565e6242b1a31 Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 16:15:28 -0700 Subject: [PATCH 04/30] Remove stray comment --- test-data/unit/check-inference.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 4f0010bba688..82b9ac65b739 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -692,7 +692,7 @@ a = [1] b = ['b'] i(a, a, b) i(b, a, b) -i(a, b, b) # error: Argument 1 to "i" has incompatible type List[int]; expected List[str] +i(a, b, b) [out] main:5: error: "str" not callable main:6: error: "object" not callable From 1bf5a935d99bfa908b4442993fee69cca9984b1e Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 17:19:52 -0700 Subject: [PATCH 05/30] Split tests on ' # ' instead of '#' --- mypy/test/data.py | 2 +- test-data/unit/check-abstract.test | 2 +- test-data/unit/check-inference.test | 27 ++++++++------------------- test-data/unit/check-lists.test | 11 ----------- test-data/unit/check-overloading.test | 12 +++--------- test-data/unit/check-varargs.test | 16 ++++++++++------ 6 files changed, 23 insertions(+), 47 deletions(-) diff --git a/mypy/test/data.py b/mypy/test/data.py index a0573d55479c..cf4a4d6f14b8 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -383,7 +383,7 @@ def expand_errors(input: List[str], output: List[str], fnam: str) -> None: for i in range(len(input)): # The first in the split things isn't a comment - for possible_err_comment in input[i].split('#')[1:]: + for possible_err_comment in input[i].split(' # ')[1:]: m = re.search( '^([ENW]):((?P\d+):)? (?P.*)$', possible_err_comment.strip()) diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 2de9d129436b..9a0d4afc5971 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -154,7 +154,7 @@ class A(metaclass=ABCMeta): @abstractmethod def g(self): pass class B(A): pass -B()# E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g' +B() # E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g' [out] [case testInstantiationAbstractsInTypeForFunctions] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 82b9ac65b739..44f1f4506071 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -669,14 +669,14 @@ from typing import TypeVar, Union, List T = TypeVar('T') U = TypeVar('U') def f(x: Union[T, int], y: T) -> T: pass -f(1, 'a')() -f('a', 1)() -f('a', 'a')() -f(1, 1)() +f(1, 'a')() # E: "str" not callable +f('a', 1)() # E: "object" not callable +f('a', 'a')() # E: "str" not callable +f(1, 1)() # E: "int" not callable def g(x: Union[T, List[T]]) -> List[T]: pass def h(x: List[str]) -> None: pass -g('a')() +g('a')() # E: List[str] not callable # The next line is a case where there are multiple ways to satisfy a constraint # involving a Union. Either T = List[str] or T = str would turn out to be valid, @@ -684,7 +684,8 @@ g('a')() # to backtrack later) and defaults to T = . The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) +g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant + h(g(['a'])) def i(x: Union[List[T], List[U]], y: List[T], z: List[U]) -> None: pass @@ -692,19 +693,7 @@ a = [1] b = ['b'] i(a, a, b) i(b, a, b) -i(a, b, b) -[out] -main:5: error: "str" not callable -main:6: error: "object" not callable -main:7: error: "str" not callable -main:8: error: "int" not callable -main:12: error: List[str] not callable -main:20: error: Argument 1 to "g" has incompatible type List[str]; expected List[] -main:20: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance -main:20: note: Consider using "Sequence" instead, which is covariant -main:28: error: Argument 1 to "i" has incompatible type List[int]; expected List[str] -main:28: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance -main:28: note: Consider using "Sequence" instead, which is covariant +i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testCallableListJoinInference] diff --git a/test-data/unit/check-lists.test b/test-data/unit/check-lists.test index 7bb0c8affdbe..c9c67e80d4fb 100644 --- a/test-data/unit/check-lists.test +++ b/test-data/unit/check-lists.test @@ -70,14 +70,3 @@ reveal_type(b) # E: Revealed type is 'builtins.list[builtins.int*]' c = [*a, 0] reveal_type(c) # E: Revealed type is 'builtins.list[builtins.int*]' [builtins fixtures/list.pyi] - -[case testListInvarianceNote] -from typing import List, Union -def f(numbers: List[Union[int, float]]) -> None: pass -a = [1, 2] -f(a) -[out] -main:4: error: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] -main:4: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance -main:4: note: Consider using "Sequence" instead, which is covariant -[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index b8ddf0101c43..6c22da05fc5f 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -608,15 +608,9 @@ n = f(list_int) m = f(list_str) n = 1 m = 1 -n = 'x' -m = 'x' -f(list_object) -[out] -tmp/foo.pyi:13: error: Incompatible types in assignment (expression has type "str", variable has type "int") -tmp/foo.pyi:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") -tmp/foo.pyi:15: error: Argument 1 to "f" has incompatible type List[object]; expected List[int] -tmp/foo.pyi:15: note: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance -tmp/foo.pyi:15: note: Consider using "Sequence" instead, which is covariant +n = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +m = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index f5b1dfbeadf1..b257a397e915 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -594,13 +594,17 @@ C().foo() C().foo(1) # The decorator's return type says this should be okay -[case testInvariantDictNote] +[case testInvariantDictArgNote] from typing import Dict, Sequence def f(a: Dict[str, Sequence[int]]) -> None: pass a = {'a': [1, 2]} -f(a) -[out] -main:4: error: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] -main:4: note: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance -main:4: note: Consider using "Mapping" instead, which is covariant +f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] # N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Mapping" instead, which is covariant [builtins fixtures/dict.pyi] + + +[case testInvariantListArgNote] +from typing import List, Union +def f(numbers: List[Union[int, float]]) -> None: pass +a = [1, 2] +f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant +[builtins fixtures/list.pyi] From 5a70c578686eca52ef70fb819c842ce304986c04 Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 17:28:12 -0700 Subject: [PATCH 06/30] Move variance to new page --- docs/source/common_issues.rst | 115 ---------------------------------- docs/source/variance.rst | 114 +++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 115 deletions(-) create mode 100644 docs/source/variance.rst diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 2501acd28fc0..ab9b67b7280d 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -180,121 +180,6 @@ not support ``sort()``) as a list and sort it in-place: # Type of x is List[int] here. x.sort() # Okay! -.. _invariance-vs-covariance: - -Invariance vs covariance ------------------------- - -Most mutable generic collections are invariant, and mypy considers all -user-defined generic classes invariant by default -(see :ref:`variance-of-generics` for motivation). This could lead to some -unexpected errors when combined with type inference. For example: - -.. code-block:: python - - class A: ... - class B(A): ... - - lst = [A(), A()] # Inferred type is List[A] - new_lst = [B(), B()] # inferred type is List[B] - lst = new_lst # mypy will complain about this, because List is invariant - -Possible strategies in such situations are: - -* Use an explicit type annotation: - - .. code-block:: python - - new_lst: List[A] = [B(), B()] - lst = new_lst # OK - -* Make a copy of the right hand side: - - .. code-block:: python - - lst = list(new_lst) # Also OK - -* Use immutable collections as annotations whenever possible: - - .. code-block:: python - - def f_bad(x: List[A]) -> A: - return x[0] - f_bad(new_lst) # Fails - - def f_good(x: Sequence[A]) -> A: - return x[0] - f_good(new_lst) # OK - -Declaring a supertype as variable type --------------------------------------- - -Sometimes the inferred type is a subtype (subclass) of the desired -type. The type inference uses the first assignment to infer the type -of a name (assume here that ``Shape`` is the base class of both -``Circle`` and ``Triangle``): - -.. code-block:: python - - shape = Circle() # Infer shape to be Circle - ... - shape = Triangle() # Type error: Triangle is not a Circle - -You can just give an explicit type for the variable in cases such the -above example: - -.. code-block:: python - - shape = Circle() # type: Shape # The variable s can be any Shape, - # not just Circle - ... - shape = Triangle() # OK - -Complex type tests ------------------- - -Mypy can usually infer the types correctly when using ``isinstance()`` -type tests, but for other kinds of checks you may need to add an -explicit type cast: - -.. code-block:: python - - def f(o: object) -> None: - if type(o) is int: - o = cast(int, o) - g(o + 1) # This would be an error without the cast - ... - else: - ... - -.. note:: - - Note that the ``object`` type used in the above example is similar - to ``Object`` in Java: it only supports operations defined for *all* - objects, such as equality and ``isinstance()``. The type ``Any``, - in contrast, supports all operations, even if they may fail at - runtime. The cast above would have been unnecessary if the type of - ``o`` was ``Any``. - -Mypy can't infer the type of ``o`` after the ``type()`` check -because it only knows about ``isinstance()`` (and the latter is better -style anyway). We can write the above code without a cast by using -``isinstance()``: - -.. code-block:: python - - def f(o: object) -> None: - if isinstance(o, int): # Mypy understands isinstance checks - g(o + 1) # Okay; type of o is inferred as int here - ... - -Type inference in mypy is designed to work well in common cases, to be -predictable and to let the type checker give useful error -messages. More powerful type inference strategies often have complex -and difficult-to-predict failure modes and could result in very -confusing error messages. The tradeoff is that you as a programmer -sometimes have to give the type checker a little help. - .. _version_and_platform_checks: Python version and system platform checks diff --git a/docs/source/variance.rst b/docs/source/variance.rst new file mode 100644 index 000000000000..f5c70bf04943 --- /dev/null +++ b/docs/source/variance.rst @@ -0,0 +1,114 @@ +.. _invariance-vs-covariance: + +Invariance vs covariance +------------------------ + +Most mutable generic collections are invariant, and mypy considers all +user-defined generic classes invariant by default +(see :ref:`variance-of-generics` for motivation). This could lead to some +unexpected errors when combined with type inference. For example: + +.. code-block:: python + + class A: ... + class B(A): ... + + lst = [A(), A()] # Inferred type is List[A] + new_lst = [B(), B()] # inferred type is List[B] + lst = new_lst # mypy will complain about this, because List is invariant + +Possible strategies in such situations are: + +* Use an explicit type annotation: + + .. code-block:: python + + new_lst: List[A] = [B(), B()] + lst = new_lst # OK + +* Make a copy of the right hand side: + + .. code-block:: python + + lst = list(new_lst) # Also OK + +* Use immutable collections as annotations whenever possible: + + .. code-block:: python + + def f_bad(x: List[A]) -> A: + return x[0] + f_bad(new_lst) # Fails + + def f_good(x: Sequence[A]) -> A: + return x[0] + f_good(new_lst) # OK + +Declaring a supertype as variable type +-------------------------------------- + +Sometimes the inferred type is a subtype (subclass) of the desired +type. The type inference uses the first assignment to infer the type +of a name (assume here that ``Shape`` is the base class of both +``Circle`` and ``Triangle``): + +.. code-block:: python + + shape = Circle() # Infer shape to be Circle + ... + shape = Triangle() # Type error: Triangle is not a Circle + +You can just give an explicit type for the variable in cases such the +above example: + +.. code-block:: python + + shape = Circle() # type: Shape # The variable s can be any Shape, + # not just Circle + ... + shape = Triangle() # OK + +Complex type tests +------------------ + +Mypy can usually infer the types correctly when using ``isinstance()`` +type tests, but for other kinds of checks you may need to add an +explicit type cast: + +.. code-block:: python + + def f(o: object) -> None: + if type(o) is int: + o = cast(int, o) + g(o + 1) # This would be an error without the cast + ... + else: + ... + +.. note:: + + Note that the ``object`` type used in the above example is similar + to ``Object`` in Java: it only supports operations defined for *all* + objects, such as equality and ``isinstance()``. The type ``Any``, + in contrast, supports all operations, even if they may fail at + runtime. The cast above would have been unnecessary if the type of + ``o`` was ``Any``. + +Mypy can't infer the type of ``o`` after the ``type()`` check +because it only knows about ``isinstance()`` (and the latter is better +style anyway). We can write the above code without a cast by using +``isinstance()``: + +.. code-block:: python + + def f(o: object) -> None: + if isinstance(o, int): # Mypy understands isinstance checks + g(o + 1) # Okay; type of o is inferred as int here + ... + +Type inference in mypy is designed to work well in common cases, to be +predictable and to let the type checker give useful error +messages. More powerful type inference strategies often have complex +and difficult-to-predict failure modes and could result in very +confusing error messages. The tradeoff is that you as a programmer +sometimes have to give the type checker a little help. From b0536d53e3d396c67d31890f5f571b633632d66b Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 17:33:56 -0700 Subject: [PATCH 07/30] Update doc url in invariant notes --- mypy/messages.py | 2 +- test-data/unit/check-inference.test | 4 ++-- test-data/unit/check-overloading.test | 2 +- test-data/unit/check-varargs.test | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 1979a8ece2c2..ec4af77e2cd3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1000,6 +1000,6 @@ def append_invariance_message(notes: List[str], suggested_alternative: str) -> List[str]: notes.append( '"' + invariant_type + '" is invariant --- see ' + - 'http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance') + 'http://mypy.readthedocs.io/en/stable/variance.html') notes.append('Consider using "' + suggested_alternative + '" instead, which is covariant') return notes diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 44f1f4506071..f6b3db000a3a 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -684,7 +684,7 @@ g('a')() # E: List[str] not callable # to backtrack later) and defaults to T = . The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant +g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant h(g(['a'])) @@ -693,7 +693,7 @@ a = [1] b = ['b'] i(a, a, b) i(b, a, b) -i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant +i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testCallableListJoinInference] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 6c22da05fc5f..9e598d328f09 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -610,7 +610,7 @@ n = 1 m = 1 n = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") m = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant +f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index b257a397e915..0a9b2abd2e96 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -598,7 +598,7 @@ C().foo(1) # The decorator's return type says this should be okay from typing import Dict, Sequence def f(a: Dict[str, Sequence[int]]) -> None: pass a = {'a': [1, 2]} -f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] # N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Mapping" instead, which is covariant +f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] # N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Mapping" instead, which is covariant [builtins fixtures/dict.pyi] @@ -606,5 +606,5 @@ f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected from typing import List, Union def f(numbers: List[Union[int, float]]) -> None: pass a = [1, 2] -f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance # N: Consider using "Sequence" instead, which is covariant +f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] From fa4093dc3e097b34650720b7c7ea3686669336a2 Mon Sep 17 00:00:00 2001 From: quartox Date: Mon, 22 May 2017 18:52:36 -0700 Subject: [PATCH 08/30] Revert to split on '#' for check tests --- mypy/test/data.py | 2 +- test-data/unit/check-abstract.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/data.py b/mypy/test/data.py index cf4a4d6f14b8..a0573d55479c 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -383,7 +383,7 @@ def expand_errors(input: List[str], output: List[str], fnam: str) -> None: for i in range(len(input)): # The first in the split things isn't a comment - for possible_err_comment in input[i].split(' # ')[1:]: + for possible_err_comment in input[i].split('#')[1:]: m = re.search( '^([ENW]):((?P\d+):)? (?P.*)$', possible_err_comment.strip()) diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 9a0d4afc5971..2de9d129436b 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -154,7 +154,7 @@ class A(metaclass=ABCMeta): @abstractmethod def g(self): pass class B(A): pass -B() # E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g' +B()# E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g' [out] [case testInstantiationAbstractsInTypeForFunctions] From 2888c5fe5e493b8454f568e5bf6197837733f3cb Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 11:12:29 -0700 Subject: [PATCH 09/30] Add variance to toc --- docs/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 90cc74941da8..0c51ddc9254a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -24,6 +24,7 @@ Mypy is a static type checker for Python. casts duck_type_compatibility common_issues + variance generics supported_python_features additional_features From f8700c3a6278b930b93bd54ddb91ad3360fd3945 Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 11:24:18 -0700 Subject: [PATCH 10/30] Move docs to correct files --- docs/source/common_issues.rst | 69 ++++++++++++++++++++++++++++++++++ docs/source/variance.rst | 71 +---------------------------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index ab9b67b7280d..3477d906bc46 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -180,6 +180,75 @@ not support ``sort()``) as a list and sort it in-place: # Type of x is List[int] here. x.sort() # Okay! +Declaring a supertype as variable type +-------------------------------------- + +Sometimes the inferred type is a subtype (subclass) of the desired +type. The type inference uses the first assignment to infer the type +of a name (assume here that ``Shape`` is the base class of both +``Circle`` and ``Triangle``): + +.. code-block:: python + + shape = Circle() # Infer shape to be Circle + ... + shape = Triangle() # Type error: Triangle is not a Circle + +You can just give an explicit type for the variable in cases such the +above example: + +.. code-block:: python + + shape = Circle() # type: Shape # The variable s can be any Shape, + # not just Circle + ... + shape = Triangle() # OK + +Complex type tests +------------------ + +Mypy can usually infer the types correctly when using ``isinstance()`` +type tests, but for other kinds of checks you may need to add an +explicit type cast: + +.. code-block:: python + + def f(o: object) -> None: + if type(o) is int: + o = cast(int, o) + g(o + 1) # This would be an error without the cast + ... + else: + ... + +.. note:: + + Note that the ``object`` type used in the above example is similar + to ``Object`` in Java: it only supports operations defined for *all* + objects, such as equality and ``isinstance()``. The type ``Any``, + in contrast, supports all operations, even if they may fail at + runtime. The cast above would have been unnecessary if the type of + ``o`` was ``Any``. + +Mypy can't infer the type of ``o`` after the ``type()`` check +because it only knows about ``isinstance()`` (and the latter is better +style anyway). We can write the above code without a cast by using +``isinstance()``: + +.. code-block:: python + + def f(o: object) -> None: + if isinstance(o, int): # Mypy understands isinstance checks + g(o + 1) # Okay; type of o is inferred as int here + ... + +Type inference in mypy is designed to work well in common cases, to be +predictable and to let the type checker give useful error +messages. More powerful type inference strategies often have complex +and difficult-to-predict failure modes and could result in very +confusing error messages. The tradeoff is that you as a programmer +sometimes have to give the type checker a little help. + .. _version_and_platform_checks: Python version and system platform checks diff --git a/docs/source/variance.rst b/docs/source/variance.rst index f5c70bf04943..4ce2584ccb3d 100644 --- a/docs/source/variance.rst +++ b/docs/source/variance.rst @@ -1,7 +1,7 @@ .. _invariance-vs-covariance: Invariance vs covariance ------------------------- +======================== Most mutable generic collections are invariant, and mypy considers all user-defined generic classes invariant by default @@ -43,72 +43,3 @@ Possible strategies in such situations are: def f_good(x: Sequence[A]) -> A: return x[0] f_good(new_lst) # OK - -Declaring a supertype as variable type --------------------------------------- - -Sometimes the inferred type is a subtype (subclass) of the desired -type. The type inference uses the first assignment to infer the type -of a name (assume here that ``Shape`` is the base class of both -``Circle`` and ``Triangle``): - -.. code-block:: python - - shape = Circle() # Infer shape to be Circle - ... - shape = Triangle() # Type error: Triangle is not a Circle - -You can just give an explicit type for the variable in cases such the -above example: - -.. code-block:: python - - shape = Circle() # type: Shape # The variable s can be any Shape, - # not just Circle - ... - shape = Triangle() # OK - -Complex type tests ------------------- - -Mypy can usually infer the types correctly when using ``isinstance()`` -type tests, but for other kinds of checks you may need to add an -explicit type cast: - -.. code-block:: python - - def f(o: object) -> None: - if type(o) is int: - o = cast(int, o) - g(o + 1) # This would be an error without the cast - ... - else: - ... - -.. note:: - - Note that the ``object`` type used in the above example is similar - to ``Object`` in Java: it only supports operations defined for *all* - objects, such as equality and ``isinstance()``. The type ``Any``, - in contrast, supports all operations, even if they may fail at - runtime. The cast above would have been unnecessary if the type of - ``o`` was ``Any``. - -Mypy can't infer the type of ``o`` after the ``type()`` check -because it only knows about ``isinstance()`` (and the latter is better -style anyway). We can write the above code without a cast by using -``isinstance()``: - -.. code-block:: python - - def f(o: object) -> None: - if isinstance(o, int): # Mypy understands isinstance checks - g(o + 1) # Okay; type of o is inferred as int here - ... - -Type inference in mypy is designed to work well in common cases, to be -predictable and to let the type checker give useful error -messages. More powerful type inference strategies often have complex -and difficult-to-predict failure modes and could result in very -confusing error messages. The tradeoff is that you as a programmer -sometimes have to give the type checker a little help. From 572160dc195403e58ef1cca4f8bc135c6f4b988c Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 12:56:31 -0700 Subject: [PATCH 11/30] Switch order in toc --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 0c51ddc9254a..1106053e2822 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -24,8 +24,8 @@ Mypy is a static type checker for Python. casts duck_type_compatibility common_issues - variance generics + variance supported_python_features additional_features command_line From 900a92784ce1dfd1ad5d16e3d226145f21940ba0 Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 14:24:25 -0700 Subject: [PATCH 12/30] Revert docs --- docs/source/common_issues.rst | 46 +++++++++++++++++++++++++++++++++++ docs/source/index.rst | 1 - docs/source/variance.rst | 45 ---------------------------------- 3 files changed, 46 insertions(+), 46 deletions(-) delete mode 100644 docs/source/variance.rst diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 3477d906bc46..2501acd28fc0 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -180,6 +180,52 @@ not support ``sort()``) as a list and sort it in-place: # Type of x is List[int] here. x.sort() # Okay! +.. _invariance-vs-covariance: + +Invariance vs covariance +------------------------ + +Most mutable generic collections are invariant, and mypy considers all +user-defined generic classes invariant by default +(see :ref:`variance-of-generics` for motivation). This could lead to some +unexpected errors when combined with type inference. For example: + +.. code-block:: python + + class A: ... + class B(A): ... + + lst = [A(), A()] # Inferred type is List[A] + new_lst = [B(), B()] # inferred type is List[B] + lst = new_lst # mypy will complain about this, because List is invariant + +Possible strategies in such situations are: + +* Use an explicit type annotation: + + .. code-block:: python + + new_lst: List[A] = [B(), B()] + lst = new_lst # OK + +* Make a copy of the right hand side: + + .. code-block:: python + + lst = list(new_lst) # Also OK + +* Use immutable collections as annotations whenever possible: + + .. code-block:: python + + def f_bad(x: List[A]) -> A: + return x[0] + f_bad(new_lst) # Fails + + def f_good(x: Sequence[A]) -> A: + return x[0] + f_good(new_lst) # OK + Declaring a supertype as variable type -------------------------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 1106053e2822..90cc74941da8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -25,7 +25,6 @@ Mypy is a static type checker for Python. duck_type_compatibility common_issues generics - variance supported_python_features additional_features command_line diff --git a/docs/source/variance.rst b/docs/source/variance.rst deleted file mode 100644 index 4ce2584ccb3d..000000000000 --- a/docs/source/variance.rst +++ /dev/null @@ -1,45 +0,0 @@ -.. _invariance-vs-covariance: - -Invariance vs covariance -======================== - -Most mutable generic collections are invariant, and mypy considers all -user-defined generic classes invariant by default -(see :ref:`variance-of-generics` for motivation). This could lead to some -unexpected errors when combined with type inference. For example: - -.. code-block:: python - - class A: ... - class B(A): ... - - lst = [A(), A()] # Inferred type is List[A] - new_lst = [B(), B()] # inferred type is List[B] - lst = new_lst # mypy will complain about this, because List is invariant - -Possible strategies in such situations are: - -* Use an explicit type annotation: - - .. code-block:: python - - new_lst: List[A] = [B(), B()] - lst = new_lst # OK - -* Make a copy of the right hand side: - - .. code-block:: python - - lst = list(new_lst) # Also OK - -* Use immutable collections as annotations whenever possible: - - .. code-block:: python - - def f_bad(x: List[A]) -> A: - return x[0] - f_bad(new_lst) # Fails - - def f_good(x: Sequence[A]) -> A: - return x[0] - f_good(new_lst) # OK From 2fee5be9ec4850018017ee18aa9fc4bc7036da03 Mon Sep 17 00:00:00 2001 From: quartox Date: Wed, 24 May 2017 15:13:54 -0700 Subject: [PATCH 13/30] Improve test readability --- test-data/unit/check-inference.test | 8 ++++++-- test-data/unit/check-overloading.test | 4 +++- test-data/unit/check-varargs.test | 8 ++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index f6b3db000a3a..3c8cd91980f7 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -684,7 +684,9 @@ g('a')() # E: List[str] not callable # to backtrack later) and defaults to T = . The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant +g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: Consider using "Sequence" instead, which is covariant h(g(['a'])) @@ -693,7 +695,9 @@ a = [1] b = ['b'] i(a, a, b) i(b, a, b) -i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant +i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testCallableListJoinInference] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 9e598d328f09..096de3d47ea6 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -610,7 +610,9 @@ n = 1 m = 1 n = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") m = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant +f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 0a9b2abd2e96..f2567e97dbdb 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -598,7 +598,9 @@ C().foo(1) # The decorator's return type says this should be okay from typing import Dict, Sequence def f(a: Dict[str, Sequence[int]]) -> None: pass a = {'a': [1, 2]} -f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] # N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Mapping" instead, which is covariant +f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \ +# N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: Consider using "Mapping" instead, which is covariant [builtins fixtures/dict.pyi] @@ -606,5 +608,7 @@ f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected from typing import List, Union def f(numbers: List[Union[int, float]]) -> None: pass a = [1, 2] -f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] # N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html # N: Consider using "Sequence" instead, which is covariant +f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] From e22c4d4679ce79501046673ac0a2be276419adb9 Mon Sep 17 00:00:00 2001 From: quartox Date: Fri, 26 May 2017 15:42:15 -0700 Subject: [PATCH 14/30] Update documentation url --- mypy/messages.py | 5 +++-- test-data/unit/check-inference.test | 4 ++-- test-data/unit/check-overloading.test | 2 +- test-data/unit/check-varargs.test | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 857efec61e7a..dd1512fab2ce 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1013,10 +1013,11 @@ def append_invariance_message(notes: List[str], suggested_alternative: str) -> List[str]: notes.append( '"' + invariant_type + '" is invariant --- see ' + - 'http://mypy.readthedocs.io/en/stable/variance.html') + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') notes.append('Consider using "' + suggested_alternative + '" instead, which is covariant') return notes + def make_inferred_type_note(context: Context, subtype: Type, supertype: Type, supertype_str: str) -> str: """Explain that the user may have forgotten to type a variable. @@ -1042,4 +1043,4 @@ def make_inferred_type_note(context: Context, subtype: Type, var_name = context.expr.name return 'Perhaps you need a type annotation for "{}"? Suggestion: {}'.format( var_name, supertype_str) - return '' \ No newline at end of file + return '' diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 3c8cd91980f7..4dcfe4d87ac7 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -685,7 +685,7 @@ g('a')() # E: List[str] not callable # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Sequence" instead, which is covariant h(g(['a'])) @@ -696,7 +696,7 @@ b = ['b'] i(a, a, b) i(b, a, b) i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 096de3d47ea6..c2a2541b3ee4 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -611,7 +611,7 @@ m = 1 n = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") m = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index f2567e97dbdb..e53485b00aad 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -599,7 +599,7 @@ from typing import Dict, Sequence def f(a: Dict[str, Sequence[int]]) -> None: pass a = {'a': [1, 2]} f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \ -# N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Mapping" instead, which is covariant [builtins fixtures/dict.pyi] @@ -609,6 +609,6 @@ from typing import List, Union def f(numbers: List[Union[int, float]]) -> None: pass a = [1, 2] f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/stable/variance.html \ +# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] From add8811e7c7bda5edafd9ee8ff15090d0d155e4e Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 07:16:27 -0700 Subject: [PATCH 15/30] Mapping is covariant in value only --- mypy/messages.py | 14 +++++++------- test-data/unit/check-varargs.test | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index dd1512fab2ce..0928a104f034 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -998,23 +998,23 @@ def pretty_or(args: List[str]) -> str: def invariance_notes(notes: List[str], arg_type: str, expected_type: str) -> List[str]: - '''Explain that the type is invariant and give notes for how to solve the issue.''' + """Explain that the type is invariant and give notes for how to solve the issue.""" if expected_type.startswith('List') and arg_type.startswith('List'): - notes = append_invariance_message(notes, 'List', 'Sequence') + notes = append_invariance_link(notes, 'List') + notes.append('Consider using "Sequence" instead, which is covariant') elif (expected_type.startswith('Dict') and arg_type.startswith('Dict') and expected_type.split(',')[0] == arg_type.split(',')[0] and expected_type.split(',')[1:] != arg_type.split(',')[1:]): - notes = append_invariance_message(notes, 'Dict', 'Mapping') + notes = append_invariance_link(notes, 'Dict') + notes.append('Consider using "Mapping" instead, which is covariant in the value') return notes -def append_invariance_message(notes: List[str], - invariant_type: str, - suggested_alternative: str) -> List[str]: +def append_invariance_link(notes: List[str], + invariant_type: str) -> List[str]: notes.append( '"' + invariant_type + '" is invariant --- see ' + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') - notes.append('Consider using "' + suggested_alternative + '" instead, which is covariant') return notes diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index e53485b00aad..dea89d297a0a 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -600,7 +600,7 @@ def f(a: Dict[str, Sequence[int]]) -> None: pass a = {'a': [1, 2]} f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \ # N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Mapping" instead, which is covariant +# N: Consider using "Mapping" instead, which is covariant in the value [builtins fixtures/dict.pyi] From c31d6e4323790744312a74d755cdf585432e9338 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 13:51:14 -0700 Subject: [PATCH 16/30] Check type fullname --- mypy/messages.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 0928a104f034..8b6f1571aff0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -572,7 +572,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: arg_type_str = '**' + arg_type_str msg = 'Argument {} {}has incompatible type {}; expected {}'.format( n, target, arg_type_str, expected_type_str) - notes = invariance_notes(notes, arg_type_str, expected_type_str) + notes = invariance_notes(notes, arg_type, expected_type) self.fail(msg, context) if notes: for note_msg in notes: @@ -997,24 +997,27 @@ def pretty_or(args: List[str]) -> str: return ", ".join(quoted[:-1]) + ", or " + quoted[-1] -def invariance_notes(notes: List[str], arg_type: str, expected_type: str) -> List[str]: +def invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" - if expected_type.startswith('List') and arg_type.startswith('List'): - notes = append_invariance_link(notes, 'List') + if (arg_type.type.fullname() == 'builtins.list' and + expected_type.type.fullname() == 'builtins.list'): + notes.append( + '"List" is invariant --- see ' + + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') notes.append('Consider using "Sequence" instead, which is covariant') - elif (expected_type.startswith('Dict') and arg_type.startswith('Dict') and - expected_type.split(',')[0] == arg_type.split(',')[0] and - expected_type.split(',')[1:] != arg_type.split(',')[1:]): - notes = append_invariance_link(notes, 'Dict') + elif (arg_type.type.fullname() == 'builtins.dict' and + expected_type.type.fullname() == 'builtins.dict' and + arg_type.args[0].type == expected_type.args[0].type and + arg_type.args[1].type != expected_type.args[1].type): + notes.append( + '"Dict" is invariant --- see ' + + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') notes.append('Consider using "Mapping" instead, which is covariant in the value') return notes def append_invariance_link(notes: List[str], invariant_type: str) -> List[str]: - notes.append( - '"' + invariant_type + '" is invariant --- see ' + - 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') return notes From b938394f33406dde37e14b624815e3ab8e25c4a6 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 13:52:32 -0700 Subject: [PATCH 17/30] Remove unused function --- mypy/messages.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 8b6f1571aff0..f28491d3796f 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1016,11 +1016,6 @@ def invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> L return notes -def append_invariance_link(notes: List[str], - invariant_type: str) -> List[str]: - return notes - - def make_inferred_type_note(context: Context, subtype: Type, supertype: Type, supertype_str: str) -> str: """Explain that the user may have forgotten to type a variable. From fbdc381b6a410e50b1b91250f08426c31e6b326a Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 14:09:25 -0700 Subject: [PATCH 18/30] Add Instance check and DRY --- mypy/messages.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index f28491d3796f..b90f644e1985 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -999,20 +999,23 @@ def pretty_or(args: List[str]) -> str: def invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" - if (arg_type.type.fullname() == 'builtins.list' and - expected_type.type.fullname() == 'builtins.list'): + if isinstance(arg_type, Instance) and isinstance(expected_type, Instance): + if (arg_type.type.fullname() == 'builtins.list' and + expected_type.type.fullname() == 'builtins.list'): + invariant_type = 'List' + covariant_suggestion = 'Consider using "Sequence" instead, which is covariant' + elif (arg_type.type.fullname() == 'builtins.dict' and + expected_type.type.fullname() == 'builtins.dict' and + arg_type.args[0].type == expected_type.args[0].type and + arg_type.args[1].type != expected_type.args[1].type): + invariant_type = 'Dict' + covariant_suggestion = ('Consider using "Mapping" instead, ' + 'which is covariant in the value') + if invariant_type: notes.append( - '"List" is invariant --- see ' + + '"{}" is invariant --- see '.format(invariant_type) + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') - notes.append('Consider using "Sequence" instead, which is covariant') - elif (arg_type.type.fullname() == 'builtins.dict' and - expected_type.type.fullname() == 'builtins.dict' and - arg_type.args[0].type == expected_type.args[0].type and - arg_type.args[1].type != expected_type.args[1].type): - notes.append( - '"Dict" is invariant --- see ' + - 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') - notes.append('Consider using "Mapping" instead, which is covariant in the value') + notes.append(covariant_suggestion) return notes From c2810e35cd1c330e2e3e286656dc94eda999e938 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 14:35:10 -0700 Subject: [PATCH 19/30] Check for subtypes --- mypy/messages.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index b90f644e1985..7637112d8e35 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -999,19 +999,23 @@ def pretty_or(args: List[str]) -> str: def invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" + from mypy.subtypes import is_subtype + invariant_type = '' + covariant_suggestion = '' if isinstance(arg_type, Instance) and isinstance(expected_type, Instance): if (arg_type.type.fullname() == 'builtins.list' and - expected_type.type.fullname() == 'builtins.list'): + expected_type.type.fullname() == 'builtins.list' and + is_subtype(arg_type.args[0], expected_type.args[0])): invariant_type = 'List' covariant_suggestion = 'Consider using "Sequence" instead, which is covariant' elif (arg_type.type.fullname() == 'builtins.dict' and expected_type.type.fullname() == 'builtins.dict' and arg_type.args[0].type == expected_type.args[0].type and - arg_type.args[1].type != expected_type.args[1].type): + is_subtype(arg_type.args[1], expected_type.args[1])): invariant_type = 'Dict' covariant_suggestion = ('Consider using "Mapping" instead, ' 'which is covariant in the value') - if invariant_type: + if invariant_type and covariant_suggestion: notes.append( '"{}" is invariant --- see '.format(invariant_type) + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') From 44a18461dd23b109186a30286b3ce6493b10e76e Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 14:37:56 -0700 Subject: [PATCH 20/30] Move Instance check to call site --- mypy/messages.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 7637112d8e35..f6564f944978 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -572,7 +572,8 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: arg_type_str = '**' + arg_type_str msg = 'Argument {} {}has incompatible type {}; expected {}'.format( n, target, arg_type_str, expected_type_str) - notes = invariance_notes(notes, arg_type, expected_type) + if isinstance(arg_type, Instance) and isinstance(expected_type, Instance): + notes = append_invariance_notes(notes, arg_type, expected_type) self.fail(msg, context) if notes: for note_msg in notes: @@ -997,24 +998,23 @@ def pretty_or(args: List[str]) -> str: return ", ".join(quoted[:-1]) + ", or " + quoted[-1] -def invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]: +def append_invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" from mypy.subtypes import is_subtype invariant_type = '' covariant_suggestion = '' - if isinstance(arg_type, Instance) and isinstance(expected_type, Instance): - if (arg_type.type.fullname() == 'builtins.list' and - expected_type.type.fullname() == 'builtins.list' and - is_subtype(arg_type.args[0], expected_type.args[0])): - invariant_type = 'List' - covariant_suggestion = 'Consider using "Sequence" instead, which is covariant' - elif (arg_type.type.fullname() == 'builtins.dict' and - expected_type.type.fullname() == 'builtins.dict' and - arg_type.args[0].type == expected_type.args[0].type and - is_subtype(arg_type.args[1], expected_type.args[1])): - invariant_type = 'Dict' - covariant_suggestion = ('Consider using "Mapping" instead, ' - 'which is covariant in the value') + if (arg_type.type.fullname() == 'builtins.list' and + expected_type.type.fullname() == 'builtins.list' and + is_subtype(arg_type.args[0], expected_type.args[0])): + invariant_type = 'List' + covariant_suggestion = 'Consider using "Sequence" instead, which is covariant' + elif (arg_type.type.fullname() == 'builtins.dict' and + expected_type.type.fullname() == 'builtins.dict' and + arg_type.args[0].type == expected_type.args[0].type and + is_subtype(arg_type.args[1], expected_type.args[1])): + invariant_type = 'Dict' + covariant_suggestion = ('Consider using "Mapping" instead, ' + 'which is covariant in the value') if invariant_type and covariant_suggestion: notes.append( '"{}" is invariant --- see '.format(invariant_type) + From bffd7a488ca2dc0d9829ffabff53251a430c6141 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 14:52:59 -0700 Subject: [PATCH 21/30] Add more test cases --- mypy/messages.py | 4 ++-- test-data/unit/check-varargs.test | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index f6564f944978..2e94270426e9 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1014,10 +1014,10 @@ def append_invariance_notes(notes: List[str], arg_type: Type, expected_type: Typ is_subtype(arg_type.args[1], expected_type.args[1])): invariant_type = 'Dict' covariant_suggestion = ('Consider using "Mapping" instead, ' - 'which is covariant in the value') + 'which is covariant in the value type') if invariant_type and covariant_suggestion: notes.append( - '"{}" is invariant --- see '.format(invariant_type) + + '"{}" is invariant -- see '.format(invariant_type) + 'http://mypy.readthedocs.io/en/latest/common_issues.html#variance') notes.append(covariant_suggestion) return notes diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index dea89d297a0a..7263f7694d29 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -596,11 +596,21 @@ C().foo(1) # The decorator's return type says this should be okay [case testInvariantDictArgNote] from typing import Dict, Sequence -def f(a: Dict[str, Sequence[int]]) -> None: pass +def f(x: Dict[str, Sequence[int]]) -> None: pass +def g(x: Dict[str, float]) -> None: pass +def h(x: Dict[str, int]) -> None: pass a = {'a': [1, 2]} +b = {'b': ['c', 'd']} +c = {'c': 1.0} +d = {'d': 1} f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \ # N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Mapping" instead, which is covariant in the value +# N: Consider using "Mapping" instead, which is covariant in the value type +f(b) # E: Argument 1 to "f" has incompatible type Dict[str, List[str]]; expected Dict[str, Sequence[int]] +g(c) +g(d) # E: Argument 1 to "g" has incompatible type Dict[str, int]; expected Dict[str, float] +h(c) # E: Argument 1 to "h" has incompatible type Dict[str, float]; expected Dict[str, int] +h(d) [builtins fixtures/dict.pyi] @@ -611,4 +621,20 @@ a = [1, 2] f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] \ # N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Sequence" instead, which is covariant +x = [1] +y = ['a'] +x = y +[builtins fixtures/list.pyi] + + +[case testInvariantTypeConfusingNames] +from typing import List, Dict, TypeVar +DictReader = TypeVar('DictReader', None) +Listener = TypeVar('Listener', None) +def f(x: Listener) -> None: pass +def g(y: DictReader) -> None: pass +a = [1, 2] +b = {'b': 1} +f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected Listener +g(b) # E: Argument 1 to "f" has incompatible type Dict[str, int]; expected DictReader [builtins fixtures/list.pyi] From 7d18a99734d5a96d0a5ba725f9567fba310a241e Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:17:54 -0700 Subject: [PATCH 22/30] Fix tests --- mypy/messages.py | 2 +- test-data/unit/check-inference.test | 8 ++------ test-data/unit/check-overloading.test | 4 +--- test-data/unit/check-varargs.test | 8 ++++---- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 2e94270426e9..ee3a388ae717 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -998,7 +998,7 @@ def pretty_or(args: List[str]) -> str: return ", ".join(quoted[:-1]) + ", or " + quoted[-1] -def append_invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]: +def append_invariance_notes(notes: List[str], arg_type: Instance, expected_type: Instance) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" from mypy.subtypes import is_subtype invariant_type = '' diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 4dcfe4d87ac7..db507af1b359 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -684,9 +684,7 @@ g('a')() # E: List[str] not callable # to backtrack later) and defaults to T = . The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Sequence" instead, which is covariant +g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] h(g(['a'])) @@ -695,9 +693,7 @@ a = [1] b = ['b'] i(a, a, b) i(b, a, b) -i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Sequence" instead, which is covariant +i(a, b, b) # E: Argument 1 to "i" has incompatible type List[int]; expected List[str] [builtins fixtures/list.pyi] [case testCallableListJoinInference] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index c2a2541b3ee4..69289fae18c1 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -610,9 +610,7 @@ n = 1 m = 1 n = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") m = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Sequence" instead, which is covariant +f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expected List[int] [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 7263f7694d29..5f31698f4b78 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -604,7 +604,7 @@ b = {'b': ['c', 'd']} c = {'c': 1.0} d = {'d': 1} f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \ -# N: "Dict" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ +# N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Mapping" instead, which is covariant in the value type f(b) # E: Argument 1 to "f" has incompatible type Dict[str, List[str]]; expected Dict[str, Sequence[int]] g(c) @@ -619,11 +619,11 @@ from typing import List, Union def f(numbers: List[Union[int, float]]) -> None: pass a = [1, 2] f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] \ -# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ +# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ # N: Consider using "Sequence" instead, which is covariant x = [1] y = ['a'] -x = y +x = y # E: Incompatible types in assignment (expression has type List[str], variable has type List[int]) [builtins fixtures/list.pyi] @@ -637,4 +637,4 @@ a = [1, 2] b = {'b': 1} f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected Listener g(b) # E: Argument 1 to "f" has incompatible type Dict[str, int]; expected DictReader -[builtins fixtures/list.pyi] +[builtins fixtures/dict.pyi] From f17d6f652cfd58f169985fb21a8a8d961805c1c5 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:29:24 -0700 Subject: [PATCH 23/30] Fix confusing names test --- test-data/unit/check-varargs.test | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 5f31698f4b78..12d6caef826f 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -608,7 +608,9 @@ f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected # N: Consider using "Mapping" instead, which is covariant in the value type f(b) # E: Argument 1 to "f" has incompatible type Dict[str, List[str]]; expected Dict[str, Sequence[int]] g(c) -g(d) # E: Argument 1 to "g" has incompatible type Dict[str, int]; expected Dict[str, float] +g(d) # E: Argument 1 to "g" has incompatible type Dict[str, int]; expected Dict[str, float] \ +# N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ +# N: Consider using "Mapping" instead, which is covariant in the value type h(c) # E: Argument 1 to "h" has incompatible type Dict[str, float]; expected Dict[str, int] h(d) [builtins fixtures/dict.pyi] @@ -628,9 +630,9 @@ x = y # E: Incompatible types in assignment (expression has type List[str], vari [case testInvariantTypeConfusingNames] -from typing import List, Dict, TypeVar -DictReader = TypeVar('DictReader', None) -Listener = TypeVar('Listener', None) +from typing import TypeVar +DictReader = TypeVar('DictReader') +Listener = TypeVar('Listener') def f(x: Listener) -> None: pass def g(y: DictReader) -> None: pass a = [1, 2] From 35905deec8c2292dfb4f3ee669416e02780f1141 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:36:32 -0700 Subject: [PATCH 24/30] Fix confusing names test and function length --- mypy/messages.py | 3 ++- test-data/unit/check-varargs.test | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index ee3a388ae717..ceb11f054637 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -998,7 +998,8 @@ def pretty_or(args: List[str]) -> str: return ", ".join(quoted[:-1]) + ", or " + quoted[-1] -def append_invariance_notes(notes: List[str], arg_type: Instance, expected_type: Instance) -> List[str]: +def append_invariance_notes(notes: List[str], arg_type: Instance, + expected_type: Instance) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" from mypy.subtypes import is_subtype invariant_type = '' diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 12d6caef826f..93ce3d9474c4 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -615,7 +615,6 @@ h(c) # E: Argument 1 to "h" has incompatible type Dict[str, float]; expected Dic h(d) [builtins fixtures/dict.pyi] - [case testInvariantListArgNote] from typing import List, Union def f(numbers: List[Union[int, float]]) -> None: pass @@ -628,11 +627,10 @@ y = ['a'] x = y # E: Incompatible types in assignment (expression has type List[str], variable has type List[int]) [builtins fixtures/list.pyi] - [case testInvariantTypeConfusingNames] from typing import TypeVar -DictReader = TypeVar('DictReader') -Listener = TypeVar('Listener') +class Listener: pass +class DictReader: pass def f(x: Listener) -> None: pass def g(y: DictReader) -> None: pass a = [1, 2] From a9a02d40e79267d3252c456b477652e744f8edc4 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:37:19 -0700 Subject: [PATCH 25/30] Remove extra space between tests --- test-data/unit/check-varargs.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 93ce3d9474c4..b093186d61e9 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -593,7 +593,6 @@ class C: C().foo() C().foo(1) # The decorator's return type says this should be okay - [case testInvariantDictArgNote] from typing import Dict, Sequence def f(x: Dict[str, Sequence[int]]) -> None: pass From 359932501b911a23f198645b7b4a18f7324343ad Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:39:19 -0700 Subject: [PATCH 26/30] Remove extra space from end of line --- test-data/unit/check-inference.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index db507af1b359..42cd312c0531 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -684,7 +684,7 @@ g('a')() # E: List[str] not callable # to backtrack later) and defaults to T = . The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] +g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[] h(g(['a'])) From de2ede08bffb855e4b957f4807affa79151167a7 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:40:13 -0700 Subject: [PATCH 27/30] Add missing quotes --- test-data/unit/check-varargs.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index b093186d61e9..06a8c5f3f1d0 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -634,6 +634,6 @@ def f(x: Listener) -> None: pass def g(y: DictReader) -> None: pass a = [1, 2] b = {'b': 1} -f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected Listener -g(b) # E: Argument 1 to "f" has incompatible type Dict[str, int]; expected DictReader +f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected "Listener" +g(b) # E: Argument 1 to "f" has incompatible type Dict[str, int]; expected "DictReader" [builtins fixtures/dict.pyi] From bfe20dcc8f0a0a64e10c34a3dbd500855450c93d Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:43:14 -0700 Subject: [PATCH 28/30] Check that arguments are Instance --- mypy/messages.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/messages.py b/mypy/messages.py index ceb11f054637..d5f0500449f0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1011,6 +1011,8 @@ def append_invariance_notes(notes: List[str], arg_type: Instance, covariant_suggestion = 'Consider using "Sequence" instead, which is covariant' elif (arg_type.type.fullname() == 'builtins.dict' and expected_type.type.fullname() == 'builtins.dict' and + isinstance(arg_type.args[0], Instance) and + isinstance(expected_type.args[0], Instance) and arg_type.args[0].type == expected_type.args[0].type and is_subtype(arg_type.args[1], expected_type.args[1])): invariant_type = 'Dict' From c225c598289b0dfddaa2a46fa6ce5d11eb6669a0 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:45:39 -0700 Subject: [PATCH 29/30] Fix error message function name --- test-data/unit/check-varargs.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 06a8c5f3f1d0..73919aa8a698 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -635,5 +635,5 @@ def g(y: DictReader) -> None: pass a = [1, 2] b = {'b': 1} f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected "Listener" -g(b) # E: Argument 1 to "f" has incompatible type Dict[str, int]; expected "DictReader" +g(b) # E: Argument 1 to "g" has incompatible type Dict[str, int]; expected "DictReader" [builtins fixtures/dict.pyi] From 739e49fb64bf8fea7390f9974f08eb1ac04995b8 Mon Sep 17 00:00:00 2001 From: quartox Date: Sat, 19 Aug 2017 15:53:47 -0700 Subject: [PATCH 30/30] Use is_same_type to check types --- mypy/messages.py | 5 ++--- test-data/unit/check-varargs.test | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index d5f0500449f0..6df38c2fc259 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1002,6 +1002,7 @@ def append_invariance_notes(notes: List[str], arg_type: Instance, expected_type: Instance) -> List[str]: """Explain that the type is invariant and give notes for how to solve the issue.""" from mypy.subtypes import is_subtype + from mypy.sametypes import is_same_type invariant_type = '' covariant_suggestion = '' if (arg_type.type.fullname() == 'builtins.list' and @@ -1011,9 +1012,7 @@ def append_invariance_notes(notes: List[str], arg_type: Instance, covariant_suggestion = 'Consider using "Sequence" instead, which is covariant' elif (arg_type.type.fullname() == 'builtins.dict' and expected_type.type.fullname() == 'builtins.dict' and - isinstance(arg_type.args[0], Instance) and - isinstance(expected_type.args[0], Instance) and - arg_type.args[0].type == expected_type.args[0].type and + is_same_type(arg_type.args[0], expected_type.args[0]) and is_subtype(arg_type.args[1], expected_type.args[1])): invariant_type = 'Dict' covariant_suggestion = ('Consider using "Mapping" instead, ' diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 73919aa8a698..d6b4cb99ee54 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -603,13 +603,13 @@ b = {'b': ['c', 'd']} c = {'c': 1.0} d = {'d': 1} f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \ -# N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Mapping" instead, which is covariant in the value type + # N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ + # N: Consider using "Mapping" instead, which is covariant in the value type f(b) # E: Argument 1 to "f" has incompatible type Dict[str, List[str]]; expected Dict[str, Sequence[int]] g(c) g(d) # E: Argument 1 to "g" has incompatible type Dict[str, int]; expected Dict[str, float] \ -# N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Mapping" instead, which is covariant in the value type + # N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ + # N: Consider using "Mapping" instead, which is covariant in the value type h(c) # E: Argument 1 to "h" has incompatible type Dict[str, float]; expected Dict[str, int] h(d) [builtins fixtures/dict.pyi] @@ -619,8 +619,8 @@ from typing import List, Union def f(numbers: List[Union[int, float]]) -> None: pass a = [1, 2] f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] \ -# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ -# N: Consider using "Sequence" instead, which is covariant + # N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \ + # N: Consider using "Sequence" instead, which is covariant x = [1] y = ['a'] x = y # E: Incompatible types in assignment (expression has type List[str], variable has type List[int])