Skip to content

Commit

Permalink
Better error message for assignments and returns incompatible due to …
Browse files Browse the repository at this point in the history
…invariance (#6803)

Fixes: #4186
  • Loading branch information
Felipe de Morais authored and ilevkivskyi committed May 9, 2019
1 parent 9c91487 commit 1ea1122
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 12 deletions.
7 changes: 6 additions & 1 deletion mypy/checker.py
Expand Up @@ -36,7 +36,7 @@
is_optional, remove_optional
)
from mypy.sametypes import is_same_type
from mypy.messages import MessageBuilder, make_inferred_type_note
from mypy.messages import MessageBuilder, make_inferred_type_note, append_invariance_notes
import mypy.checkexpr
from mypy.checkmember import (
map_type_from_supertype, bind_self, erase_to_bound, type_object_type,
Expand Down Expand Up @@ -3569,6 +3569,7 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
return False
extra_info = [] # type: List[str]
note_msg = ''
notes = [] # type: List[str]
if subtype_label is not None or supertype_label is not None:
subtype_str, supertype_str = self.msg.format_distinctly(subtype, supertype)
if subtype_label is not None:
Expand All @@ -3577,9 +3578,13 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
extra_info.append(supertype_label + ' ' + supertype_str)
note_msg = make_inferred_type_note(context, subtype,
supertype, supertype_str)
if isinstance(subtype, Instance) and isinstance(supertype, Instance):
notes = append_invariance_notes([], subtype, supertype)
if extra_info:
msg += ' (' + ', '.join(extra_info) + ')'
self.fail(msg, context)
for note in notes:
self.msg.note(note, context)
if note_msg:
self.note(note_msg, context)
if (isinstance(supertype, Instance) and supertype.type.is_protocol and
Expand Down
18 changes: 18 additions & 0 deletions test-data/unit/check-basic.test
Expand Up @@ -372,3 +372,21 @@ none = None
b = none.__bool__()
reveal_type(b) # E: Revealed type is 'builtins.bool'
[builtins fixtures/bool.pyi]

[case testAssignmentInvariantNoteForList]
from typing import List
x: List[int]
y: List[float]
y = x # E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[float]") \
# 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]

[case testAssignmentInvariantNoteForDict]
from typing import Dict
x: Dict[str, int]
y: Dict[str, float]
y = x # E: Incompatible types in assignment (expression has type "Dict[str, int]", variable has type "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
[builtins fixtures/dict.pyi]
12 changes: 9 additions & 3 deletions test-data/unit/check-functions.test
Expand Up @@ -2364,12 +2364,16 @@ from typing import Union, Dict, List
def f() -> List[Union[str, int]]:
x = ['a']
return x # E: Incompatible return value type (got "List[str]", expected "List[Union[str, int]]") \
# N: Perhaps you need a type annotation for "x"? Suggestion: "List[Union[str, int]]"
# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant \
# N: Perhaps you need a type annotation for "x"? Suggestion: "List[Union[str, int]]"

def g() -> Dict[str, Union[str, int]]:
x = {'a': 'a'}
return x # E: Incompatible return value type (got "Dict[str, str]", expected "Dict[str, Union[str, int]]") \
# N: Perhaps you need a type annotation for "x"? Suggestion: "Dict[str, Union[str, 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: Perhaps you need a type annotation for "x"? Suggestion: "Dict[str, Union[str, int]]"

def h() -> Dict[Union[str, int], str]:
x = {'a': 'a'}
Expand All @@ -2378,7 +2382,9 @@ def h() -> Dict[Union[str, int], str]:

def i() -> List[Union[int, float]]:
x: List[int] = [1]
return x # E: Incompatible return value type (got "List[int]", expected "List[Union[int, float]]")
return x # E: Incompatible return value type (got "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

[builtins fixtures/dict.pyi]

Expand Down
10 changes: 8 additions & 2 deletions test-data/unit/check-inference-context.test
Expand Up @@ -1320,7 +1320,10 @@ T = TypeVar('T', bound=int)
def f(x: List[T]) -> List[T]: ...

# TODO: improve error message for such cases, see #3283 and #5706
y: List[str] = f([]) # E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[str]")
y: List[str] = f([]) \
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "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
[builtins fixtures/list.pyi]

[case testWideOuterContextNoArgs]
Expand All @@ -1339,7 +1342,10 @@ from typing import TypeVar, Optional, List
T = TypeVar('T', bound=int)
def f(x: Optional[T] = None) -> List[T]: ...

y: List[str] = f() # E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[str]")
y: List[str] = f() \
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "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
[builtins fixtures/list.pyi]

[case testUseCovariantGenericOuterContext]
Expand Down
10 changes: 8 additions & 2 deletions test-data/unit/check-inference.test
Expand Up @@ -1331,7 +1331,10 @@ if int():
if int():
a = x2
if int():
a = x3 # E: Incompatible types in assignment (expression has type "List[B]", variable has type "List[A]")
a = x3 \
# E: Incompatible types in assignment (expression has type "List[B]", variable has type "List[A]") \
# 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]
[typing fixtures/typing-full.pyi]

Expand All @@ -1351,7 +1354,10 @@ if int():
if int():
a = x2
if int():
a = x3 # E: Incompatible types in assignment (expression has type "List[B]", variable has type "List[A]")
a = x3 \
# E: Incompatible types in assignment (expression has type "List[B]", variable has type "List[A]") \
# 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]
[typing fixtures/typing-full.pyi]

Expand Down
5 changes: 4 additions & 1 deletion test-data/unit/check-typevar-values.test
Expand Up @@ -19,7 +19,10 @@ o = [object()]
if int():
i = f(1)
s = f('')
o = f(1) # E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[object]")
o = f(1) \
# E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[object]") \
# 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]

[case testCallGenericFunctionWithTypeVarValueRestrictionAndAnyArgs]
Expand Down
17 changes: 14 additions & 3 deletions test-data/unit/check-varargs.test
Expand Up @@ -586,18 +586,29 @@ ab = None # type: List[B]
if int():
a, aa = G().f(*[a]) \
# E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") \
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[A]")
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[A]") \
# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant

if int():
aa, a = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "A")
if int():
ab, aa = G().f(*[a]) \
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[A]") \
# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant \
# E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B"

if int():
ao, ao = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[object]")
ao, ao = G().f(*[a]) \
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[object]") \
# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant
if int():
aa, aa = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[A]")
aa, aa = G().f(*[a]) \
# E: Incompatible types in assignment (expression has type "List[<nothing>]", variable has type "List[A]") \
# N: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant

class G(Generic[T]):
def f(self, *a: S) -> Tuple[List[S], List[T]]:
Expand Down

0 comments on commit 1ea1122

Please sign in to comment.