Skip to content

Commit a494197

Browse files
quartoxilevkivskyi
authored andcommitted
Mismatch of inferred type and return type note (#3428)
* Add note for diff inffered and return type * Fix conditions for note * Move tests to newline * Style improvements and more detailed message * Fix tests * Move message function to messages * More test cases * Test different sequence * Cleaner separate of test cases
1 parent 847d884 commit a494197

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed

mypy/checker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
true_only, false_only, function_type, is_named_instance, union_items
3737
)
3838
from mypy.sametypes import is_same_type, is_same_types
39-
from mypy.messages import MessageBuilder
39+
from mypy.messages import MessageBuilder, make_inferred_type_note
4040
import mypy.checkexpr
4141
from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound
4242
from mypy import messages
@@ -2313,15 +2313,20 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
23132313
if self.should_suppress_optional_error([subtype]):
23142314
return False
23152315
extra_info = [] # type: List[str]
2316+
note_msg = ''
23162317
if subtype_label is not None or supertype_label is not None:
23172318
subtype_str, supertype_str = self.msg.format_distinctly(subtype, supertype)
23182319
if subtype_label is not None:
23192320
extra_info.append(subtype_label + ' ' + subtype_str)
23202321
if supertype_label is not None:
23212322
extra_info.append(supertype_label + ' ' + supertype_str)
2323+
note_msg = make_inferred_type_note(context, subtype,
2324+
supertype, supertype_str)
23222325
if extra_info:
23232326
msg += ' (' + ', '.join(extra_info) + ')'
23242327
self.fail(msg, context)
2328+
if note_msg:
2329+
self.note(note_msg, context)
23252330
return False
23262331

23272332
def contains_none(self, t: Type) -> bool:

mypy/messages.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
)
1818
from mypy.nodes import (
1919
TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases,
20-
ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2
20+
ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2,
21+
ReturnStmt, NameExpr, Var
2122
)
2223

2324

@@ -989,3 +990,31 @@ def pretty_or(args: List[str]) -> str:
989990
if len(quoted) == 2:
990991
return "{} or {}".format(quoted[0], quoted[1])
991992
return ", ".join(quoted[:-1]) + ", or " + quoted[-1]
993+
994+
995+
def make_inferred_type_note(context: Context, subtype: Type,
996+
supertype: Type, supertype_str: str) -> str:
997+
"""Explain that the user may have forgotten to type a variable.
998+
999+
The user does not expect an error if the inferred container type is the same as the return
1000+
type of a function and the argument type(s) are a subtype of the argument type(s) of the
1001+
return type. This note suggests that they add a type annotation with the return type instead
1002+
of relying on the inferred type.
1003+
"""
1004+
from mypy.subtypes import is_subtype
1005+
if (isinstance(subtype, Instance) and
1006+
isinstance(supertype, Instance) and
1007+
subtype.type.fullname() == supertype.type.fullname() and
1008+
subtype.args and
1009+
supertype.args and
1010+
isinstance(context, ReturnStmt) and
1011+
isinstance(context.expr, NameExpr) and
1012+
isinstance(context.expr.node, Var) and
1013+
context.expr.node.is_inferred):
1014+
for subtype_arg, supertype_arg in zip(subtype.args, supertype.args):
1015+
if not is_subtype(subtype_arg, supertype_arg):
1016+
return ''
1017+
var_name = context.expr.name
1018+
return 'Perhaps you need a type annotation for "{}"? Suggestion: {}'.format(
1019+
var_name, supertype_str)
1020+
return ''

test-data/unit/check-functions.test

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,3 +2067,59 @@ def some_method(self: badtype): pass # E: Name 'badtype' is not defined
20672067
def fn(
20682068
a: badtype) -> None: # E: Name 'badtype' is not defined
20692069
pass
2070+
2071+
[case testInferredTypeSubTypeOfReturnType]
2072+
from typing import Union, Dict, List
2073+
def f() -> List[Union[str, int]]:
2074+
x = ['a']
2075+
return x # E: Incompatible return value type (got List[str], expected List[Union[str, int]]) \
2076+
# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[str, int]]
2077+
2078+
def g() -> Dict[str, Union[str, int]]:
2079+
x = {'a': 'a'}
2080+
return x # E: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[str, int]]) \
2081+
# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[str, Union[str, int]]
2082+
2083+
def h() -> Dict[Union[str, int], str]:
2084+
x = {'a': 'a'}
2085+
return x # E: Incompatible return value type (got Dict[str, str], expected Dict[Union[str, int], str]) \
2086+
# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[Union[str, int], str]
2087+
2088+
def i() -> List[Union[int, float]]:
2089+
x: List[int] = [1]
2090+
return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) \
2091+
# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, float]]
2092+
2093+
[builtins fixtures/dict.pyi]
2094+
2095+
[case testInferredTypeNotSubTypeOfReturnType]
2096+
from typing import Union, List
2097+
def f() -> List[Union[int, float]]:
2098+
x = ['a']
2099+
return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]])
2100+
2101+
def g() -> List[Union[str, int]]:
2102+
x = ('a', 2)
2103+
return x # E: Incompatible return value type (got "Tuple[str, int]", expected List[Union[str, int]])
2104+
2105+
[builtins fixtures/list.pyi]
2106+
2107+
[case testInferredTypeIsObjectMismatch]
2108+
from typing import Union, Dict, List
2109+
def f() -> Dict[str, Union[str, int]]:
2110+
x = {'a': 'a', 'b': 2}
2111+
return x # E: Incompatible return value type (got Dict[str, object], expected Dict[str, Union[str, int]])
2112+
2113+
def g() -> Dict[str, Union[str, int]]:
2114+
x: Dict[str, Union[str, int]] = {'a': 'a', 'b': 2}
2115+
return x
2116+
2117+
def h() -> List[Union[str, int]]:
2118+
x = ['a', 2]
2119+
return x # E: Incompatible return value type (got List[object], expected List[Union[str, int]])
2120+
2121+
def i() -> List[Union[str, int]]:
2122+
x: List[Union[str, int]] = ['a', 2]
2123+
return x
2124+
2125+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)