From 8e3d84201e76f9f874014e11f549afc248632f60 Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 11:09:11 -0700 Subject: [PATCH 1/9] Add note for diff inffered and return type --- mypy/checker.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 870c561852b6..94da33fc32e0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2322,6 +2322,9 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, if extra_info: msg += ' (' + ', '.join(extra_info) + ')' self.fail(msg, context) + note_msg = make_inferred_type_mismatch_note(msg, context, subtype, supertype, supertype_str) + if note_msg: + self.note(note_msg, context) return False def contains_none(self, t: Type) -> bool: @@ -3115,3 +3118,19 @@ def push_class(self, info: TypeInfo) -> Iterator[None]: @contextmanager def nothing() -> Iterator[None]: yield + + +def make_inferred_type_mismatch_note(msg: str, context: Context, subtype: Type, + supertype: Type, supertype_str: str) -> str: + '''The user does not expect an error if the inferred container type is the same as the return + type of a function and the argument type(s) are a subtype of the argument type(s) of the + return type. + ''' + if (msg.startswith(messages.INCOMPATIBLE_RETURN_VALUE_TYPE) and + subtype.type.fullname() == supertype.type.fullname() and + subtype.args and supertype.args and context.expr.node.is_inferred): + for subtype_arg, supertype_arg in zip(subtype.args, supertype.args): + if not is_subtype(subtype_arg, supertype_arg): + return '' + return 'Perhaps a type annotation is missing here? Suggestion: ' + supertype_str + return '' From 8e5e82595cfb15f97b9b54ca3ad0719f7855b094 Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 12:45:08 -0700 Subject: [PATCH 2/9] Fix conditions for note --- mypy/checker.py | 14 ++++++++++---- test-data/unit/check-functions.test | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 94da33fc32e0..94308153f4f9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2313,16 +2313,18 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, if self.should_suppress_optional_error([subtype]): return False extra_info = [] # type: List[str] + note_msg = '' 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: extra_info.append(subtype_label + ' ' + subtype_str) if supertype_label is not None: extra_info.append(supertype_label + ' ' + supertype_str) + note_msg = make_inferred_type_mismatch_note(msg, context, subtype, + supertype, supertype_str) if extra_info: msg += ' (' + ', '.join(extra_info) + ')' self.fail(msg, context) - note_msg = make_inferred_type_mismatch_note(msg, context, subtype, supertype, supertype_str) if note_msg: self.note(note_msg, context) return False @@ -3120,15 +3122,19 @@ def nothing() -> Iterator[None]: yield -def make_inferred_type_mismatch_note(msg: str, context: Context, subtype: Type, +def make_inferred_type_mismatch_note(msg: str, context: Context, subtype: Type, supertype: Type, supertype_str: str) -> str: '''The user does not expect an error if the inferred container type is the same as the return type of a function and the argument type(s) are a subtype of the argument type(s) of the - return type. + return type. This note suggests that they add a type annotation with the return type instead + of relying on the inferred type. ''' if (msg.startswith(messages.INCOMPATIBLE_RETURN_VALUE_TYPE) and + isinstance(subtype, Instance) and isinstance(supertype, Instance) and subtype.type.fullname() == supertype.type.fullname() and - subtype.args and supertype.args and context.expr.node.is_inferred): + subtype.args and supertype.args and + isinstance(context, ReturnStmt) and isinstance(context.expr, NameExpr) and + isinstance(context.expr.node, Var) and context.expr.node.is_inferred): for subtype_arg, supertype_arg in zip(subtype.args, supertype.args): if not is_subtype(subtype_arg, supertype_arg): return '' diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 030fe788c5e2..50ac5dd2f0ab 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2067,3 +2067,23 @@ def some_method(self: badtype): pass # E: Name 'badtype' is not defined def fn( a: badtype) -> None: # E: Name 'badtype' is not defined pass + +[case testInferredTypeMismatchReturnType] +from typing import Union, Dict, List +def f() -> List[Union[int, str]]: + x = ['a'] + return x # E: Incompatible return value type (got List[str], expected List[Union[int, str]]) # N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, str]] + +def g() -> Dict[str, Union[int, str]]: + x = {'a': 'a'} + return x # E: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[int, str]]) # N: Perhaps a type annotation is missing here? Suggestion: Dict[str, Union[int, str]] + +def h() -> List[Union[int, float]]: + x = ['a'] + return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]]) + +def i() -> List[Union[int, float]]: + x = [1] # Type: List[int] + return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) # N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, float]] + +[builtins fixtures/dict.pyi] From 054482de0cec538eede30134af1b0a8630661ef0 Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 20:31:16 -0700 Subject: [PATCH 3/9] Move tests to newline --- test-data/unit/check-functions.test | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 50ac5dd2f0ab..2bc2a344fd2b 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2072,11 +2072,13 @@ def fn( from typing import Union, Dict, List def f() -> List[Union[int, str]]: x = ['a'] - return x # E: Incompatible return value type (got List[str], expected List[Union[int, str]]) # N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, str]] + return x # E: Incompatible return value type (got List[str], expected List[Union[int, str]]) \ +# N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, str]] def g() -> Dict[str, Union[int, str]]: x = {'a': 'a'} - return x # E: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[int, str]]) # N: Perhaps a type annotation is missing here? Suggestion: Dict[str, Union[int, str]] + return x # E: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[int, str]]) \ +# N: Perhaps a type annotation is missing here? Suggestion: Dict[str, Union[int, str]] def h() -> List[Union[int, float]]: x = ['a'] @@ -2084,6 +2086,7 @@ def h() -> List[Union[int, float]]: def i() -> List[Union[int, float]]: x = [1] # Type: List[int] - return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) # N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, float]] + return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) \ +# N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, float]] [builtins fixtures/dict.pyi] From 162805ebdf65eb4bcf7be1ac687421fd403dbc8d Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 20:48:58 -0700 Subject: [PATCH 4/9] Style improvements and more detailed message --- mypy/checker.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 94308153f4f9..50395c18c129 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2320,8 +2320,8 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, extra_info.append(subtype_label + ' ' + subtype_str) if supertype_label is not None: extra_info.append(supertype_label + ' ' + supertype_str) - note_msg = make_inferred_type_mismatch_note(msg, context, subtype, - supertype, supertype_str) + note_msg = make_inferred_type_note(context, subtype, + supertype, supertype_str) if extra_info: msg += ' (' + ', '.join(extra_info) + ')' self.fail(msg, context) @@ -3122,21 +3122,28 @@ def nothing() -> Iterator[None]: yield -def make_inferred_type_mismatch_note(msg: str, context: Context, subtype: Type, - supertype: Type, supertype_str: str) -> str: - '''The user does not expect an error if the inferred container type is the same as the return +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. + + The user does not expect an error if the inferred container type is the same as the return type of a function and the argument type(s) are a subtype of the argument type(s) of the return type. This note suggests that they add a type annotation with the return type instead of relying on the inferred type. - ''' - if (msg.startswith(messages.INCOMPATIBLE_RETURN_VALUE_TYPE) and - isinstance(subtype, Instance) and isinstance(supertype, Instance) and - subtype.type.fullname() == supertype.type.fullname() and - subtype.args and supertype.args and - isinstance(context, ReturnStmt) and isinstance(context.expr, NameExpr) and - isinstance(context.expr.node, Var) and context.expr.node.is_inferred): + """ + if (isinstance(subtype, Instance) and + isinstance(supertype, Instance) and + subtype.type.fullname() == supertype.type.fullname() and + subtype.args and + supertype.args and + isinstance(context, ReturnStmt) and + isinstance(context.expr, NameExpr) and + isinstance(context.expr.node, Var) and + context.expr.node.is_inferred): for subtype_arg, supertype_arg in zip(subtype.args, supertype.args): if not is_subtype(subtype_arg, supertype_arg): return '' - return 'Perhaps a type annotation is missing here? Suggestion: ' + supertype_str + var_name = context.expr.name + return 'Perhaps you need a type annotation for "{}"? Suggestion: {}'.format( + var_name, supertype_str) return '' From 3e43ca48e7007974ea3512794de6dd3c77d58918 Mon Sep 17 00:00:00 2001 From: quartox Date: Tue, 23 May 2017 20:49:11 -0700 Subject: [PATCH 5/9] Fix tests --- test-data/unit/check-functions.test | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 2bc2a344fd2b..24a5cdd34a50 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2073,12 +2073,12 @@ from typing import Union, Dict, List def f() -> List[Union[int, str]]: x = ['a'] return x # E: Incompatible return value type (got List[str], expected List[Union[int, str]]) \ -# N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, str]] +# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, str]] def g() -> Dict[str, Union[int, str]]: x = {'a': 'a'} return x # E: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[int, str]]) \ -# N: Perhaps a type annotation is missing here? Suggestion: Dict[str, Union[int, str]] +# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[str, Union[int, str]] def h() -> List[Union[int, float]]: x = ['a'] @@ -2087,6 +2087,6 @@ def h() -> List[Union[int, float]]: def i() -> List[Union[int, float]]: x = [1] # Type: List[int] return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) \ -# N: Perhaps a type annotation is missing here? Suggestion: List[Union[int, float]] +# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, float]] [builtins fixtures/dict.pyi] From b5a9d38370be9554b014ebe8bc39788ef93c6a88 Mon Sep 17 00:00:00 2001 From: quartox Date: Wed, 24 May 2017 06:34:19 -0700 Subject: [PATCH 6/9] Move message function to messages --- mypy/checker.py | 29 +---------------------------- mypy/messages.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 50395c18c129..59746059a573 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -36,7 +36,7 @@ true_only, false_only, function_type, is_named_instance, union_items ) from mypy.sametypes import is_same_type, is_same_types -from mypy.messages import MessageBuilder +from mypy.messages import MessageBuilder, make_inferred_type_note import mypy.checkexpr from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound from mypy import messages @@ -3120,30 +3120,3 @@ def push_class(self, info: TypeInfo) -> Iterator[None]: @contextmanager def nothing() -> Iterator[None]: yield - - -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. - - The user does not expect an error if the inferred container type is the same as the return - type of a function and the argument type(s) are a subtype of the argument type(s) of the - return type. This note suggests that they add a type annotation with the return type instead - of relying on the inferred type. - """ - if (isinstance(subtype, Instance) and - isinstance(supertype, Instance) and - subtype.type.fullname() == supertype.type.fullname() and - subtype.args and - supertype.args and - isinstance(context, ReturnStmt) and - isinstance(context.expr, NameExpr) and - isinstance(context.expr.node, Var) and - context.expr.node.is_inferred): - for subtype_arg, supertype_arg in zip(subtype.args, supertype.args): - if not is_subtype(subtype_arg, supertype_arg): - return '' - var_name = context.expr.name - return 'Perhaps you need a type annotation for "{}"? Suggestion: {}'.format( - var_name, supertype_str) - return '' diff --git a/mypy/messages.py b/mypy/messages.py index 5c2f5d16fdc7..f4e40b680dfa 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -17,7 +17,8 @@ ) from mypy.nodes import ( TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, - ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2 + ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, + ReturnStmt, NameExpr, Var ) @@ -977,3 +978,31 @@ 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 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. + + The user does not expect an error if the inferred container type is the same as the return + type of a function and the argument type(s) are a subtype of the argument type(s) of the + return type. This note suggests that they add a type annotation with the return type instead + of relying on the inferred type. + """ + from mypy.subtypes import is_subtype + if (isinstance(subtype, Instance) and + isinstance(supertype, Instance) and + subtype.type.fullname() == supertype.type.fullname() and + subtype.args and + supertype.args and + isinstance(context, ReturnStmt) and + isinstance(context.expr, NameExpr) and + isinstance(context.expr.node, Var) and + context.expr.node.is_inferred): + for subtype_arg, supertype_arg in zip(subtype.args, supertype.args): + if not is_subtype(subtype_arg, supertype_arg): + return '' + var_name = context.expr.name + return 'Perhaps you need a type annotation for "{}"? Suggestion: {}'.format( + var_name, supertype_str) + return '' From c1132175ae2029d7a9218fded97f59a8942ab92f Mon Sep 17 00:00:00 2001 From: quartox Date: Wed, 24 May 2017 16:01:09 -0700 Subject: [PATCH 7/9] More test cases --- test-data/unit/check-functions.test | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 24a5cdd34a50..1578111e35b6 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2070,23 +2070,48 @@ def fn( [case testInferredTypeMismatchReturnType] from typing import Union, Dict, List -def f() -> List[Union[int, str]]: +def f() -> List[Union[str, int]]: x = ['a'] - return x # E: Incompatible return value type (got List[str], expected List[Union[int, str]]) \ -# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, str]] + 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]] -def g() -> Dict[str, Union[int, str]]: +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[int, str]]) \ -# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[str, Union[int, str]] + 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]] -def h() -> List[Union[int, float]]: - x = ['a'] - return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]]) +def h() -> Dict[Union[str, int], str]: + x = {'a': 'a'} + return x # E: Incompatible return value type (got Dict[str, str], expected Dict[Union[str, int], str]) \ +# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[Union[str, int], str] def i() -> List[Union[int, float]]: - x = [1] # Type: List[int] + x: List[int] = [1] return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) \ # N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, float]] +def j() -> List[Union[int, float]]: + x = ['a'] + return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]]) + +def k() -> Dict[str, Union[str, int]]: + x = {'a': 'a', 'b': 2} + return x # E: Incompatible return value type (got Dict[str, object], expected Dict[str, Union[str, int]]) + +def l() -> Dict[str, Union[str, int]]: + x: Dict[str, Union[str, int]] = {'a': 'a', 'b': 2} + return x + +def m() -> List[Union[str, int]]: + x = ['a', 2] + return x # E: Incompatible return value type (got List[object], expected List[Union[str, int]]) + +def n() -> List[Union[str, int]]: + x: List[Union[str, int]] = ['a', 2] + return x + +def o() -> Union[str, int]: + x = 'a' + return x + [builtins fixtures/dict.pyi] From 29baa4c52096fd7650980e608ac9993b1a47e0fd Mon Sep 17 00:00:00 2001 From: quartox Date: Wed, 24 May 2017 16:11:52 -0700 Subject: [PATCH 8/9] Test different sequence --- test-data/unit/check-functions.test | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 1578111e35b6..f7dba8e40472 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2094,24 +2094,28 @@ def j() -> List[Union[int, float]]: x = ['a'] return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]]) -def k() -> Dict[str, Union[str, int]]: +def k() -> List[Union[str, int]]: + x = ('a', 2) + return x # E: Incompatible return value type (got "Tuple[str, int]", expected List[Union[str, int]]) + +[builtins fixtures/dict.pyi] + +[case testInferredTypeIsObjectNotMatchUnion] +from typing import Union, Dict, List +def f() -> Dict[str, Union[str, int]]: x = {'a': 'a', 'b': 2} return x # E: Incompatible return value type (got Dict[str, object], expected Dict[str, Union[str, int]]) -def l() -> Dict[str, Union[str, int]]: +def g() -> Dict[str, Union[str, int]]: x: Dict[str, Union[str, int]] = {'a': 'a', 'b': 2} return x -def m() -> List[Union[str, int]]: +def h() -> List[Union[str, int]]: x = ['a', 2] return x # E: Incompatible return value type (got List[object], expected List[Union[str, int]]) -def n() -> List[Union[str, int]]: +def i() -> List[Union[str, int]]: x: List[Union[str, int]] = ['a', 2] return x -def o() -> Union[str, int]: - x = 'a' - return x - [builtins fixtures/dict.pyi] From 754916f1236aa276a45e81c96748323da25ce6d4 Mon Sep 17 00:00:00 2001 From: quartox Date: Wed, 24 May 2017 16:16:48 -0700 Subject: [PATCH 9/9] Cleaner separate of test cases --- test-data/unit/check-functions.test | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index f7dba8e40472..6c93a47bf676 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2068,7 +2068,7 @@ def fn( a: badtype) -> None: # E: Name 'badtype' is not defined pass -[case testInferredTypeMismatchReturnType] +[case testInferredTypeSubTypeOfReturnType] from typing import Union, Dict, List def f() -> List[Union[str, int]]: x = ['a'] @@ -2090,17 +2090,21 @@ def i() -> List[Union[int, float]]: return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) \ # N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, float]] -def j() -> List[Union[int, float]]: +[builtins fixtures/dict.pyi] + +[case testInferredTypeNotSubTypeOfReturnType] +from typing import Union, List +def f() -> List[Union[int, float]]: x = ['a'] return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]]) -def k() -> List[Union[str, int]]: +def g() -> List[Union[str, int]]: x = ('a', 2) return x # E: Incompatible return value type (got "Tuple[str, int]", expected List[Union[str, int]]) -[builtins fixtures/dict.pyi] +[builtins fixtures/list.pyi] -[case testInferredTypeIsObjectNotMatchUnion] +[case testInferredTypeIsObjectMismatch] from typing import Union, Dict, List def f() -> Dict[str, Union[str, int]]: x = {'a': 'a', 'b': 2}