From 0ed4c1ca58540874edc7f1b9246da1b4f7222d6a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 17 Sep 2021 18:54:24 +0300 Subject: [PATCH 1/5] Fixes type inference for generic calls in `if` expr --- mypy/checkexpr.py | 20 ++++++++++++++---- test-data/unit/check-inference.test | 32 ++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 30db1e8ac87d..8bf12c1d1a8c 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3836,8 +3836,10 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F allow_none_return=allow_none_return) # Analyze the right branch using full type context and store the type - full_context_else_type = self.analyze_cond_branch(else_map, e.else_expr, context=ctx, - allow_none_return=allow_none_return) + full_context_else_type = self.analyze_cond_branch(else_map, e.else_expr, + context=ctx, + allow_none_return=allow_none_return, + is_else=True) if not mypy.checker.is_valid_inferred_type(if_type): # Analyze the right branch disregarding the left branch. else_type = full_context_else_type @@ -3855,7 +3857,8 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F # Analyze the right branch in the context of the left # branch's type. else_type = self.analyze_cond_branch(else_map, e.else_expr, context=if_type, - allow_none_return=allow_none_return) + allow_none_return=allow_none_return, + is_else=True) # Only create a union type if the type context is a union, to be mostly # compatible with older mypy versions where we always did a join. @@ -3870,8 +3873,17 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F def analyze_cond_branch(self, map: Optional[Dict[Expression, Type]], node: Expression, context: Optional[Type], - allow_none_return: bool = False) -> Type: + allow_none_return: bool = False, + is_else: bool = False) -> Type: with self.chk.binder.frame_context(can_skip=True, fall_through=0): + if is_else and isinstance(node, CallExpr): + # When calling a function on the else part, + # we can face a generic function with multiple type vars. + # When inferecing it, `context` might be used instead of real args. + # Usually, we don't want that. + # https://github.com/python/mypy/issues/11049 + context = None + if map is None: # We still need to type check node, in case we want to # process it for isinstance checks later diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index c945d9c68aee..935f9de93b47 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1975,7 +1975,7 @@ T = TypeVar('T') class A: def f(self) -> None: - self.g() # E: Too few arguments for "g" of "A" + self.g() # E: Too few arguments for "g" of "A" self.g(1) @dec def g(self, x: str) -> None: pass @@ -2270,6 +2270,36 @@ a = {0: [0]} if f() else {0: []} a() # E: "Dict[int, List[int]]" not callable [builtins fixtures/dict.pyi] +[case testConditionalInferenceGenericFunctionRight] +from typing import TypeVar, Union + +T1 = TypeVar("T1") +T2 = TypeVar("T2") + +def foo(a: T1, b: T2) -> Union[T1, T2]: pass +x: bool + +reveal_type(1 if x else foo(1, "s")) # N: Revealed type is "Union[builtins.int*, builtins.str*]" +reveal_type("a" if x else foo(1, "s")) # N: Revealed type is "Union[builtins.int*, builtins.str*]" +reveal_type(1 if x else foo("s", 1)) # N: Revealed type is "Union[builtins.str*, builtins.int*]" +reveal_type("a" if x else foo("s", 1)) # N: Revealed type is "Union[builtins.str*, builtins.int*]" +[builtins fixtures/bool.pyi] + +[case testConditionalInferenceGenericFunctionLeft] +from typing import TypeVar, Union + +T1 = TypeVar("T1") +T2 = TypeVar("T2") + +def foo(a: T1, b: T2) -> Union[T1, T2]: pass +x: bool + +reveal_type(foo(1, "s") if x else 1) # N: Revealed type is "Union[builtins.int*, builtins.str*]" +reveal_type(foo(1, "s") if x else "a") # N: Revealed type is "Union[builtins.int*, builtins.str*]" +reveal_type(foo("s", 1) if x else 1) # N: Revealed type is "Union[builtins.str*, builtins.int*]" +reveal_type(foo("s", 1) if x else "a") # N: Revealed type is "Union[builtins.str*, builtins.int*]" +[builtins fixtures/bool.pyi] + [case testMisguidedSetItem] from typing import Generic, Sequence, TypeVar T = TypeVar('T') From b30c764039fb81afe04902f250e24be7b6f7e867 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 17 Sep 2021 19:40:02 +0300 Subject: [PATCH 2/5] Fixes tests --- mypy/checkexpr.py | 8 +- mypy/test/testcheck.py | 146 ++++++++++++++-------------- test-data/unit/check-inference.test | 46 +++++++++ test-data/unit/fixtures/bool.pyi | 3 +- 4 files changed, 126 insertions(+), 77 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 8bf12c1d1a8c..b499f9a87b6e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3876,20 +3876,22 @@ def analyze_cond_branch(self, map: Optional[Dict[Expression, Type]], allow_none_return: bool = False, is_else: bool = False) -> Type: with self.chk.binder.frame_context(can_skip=True, fall_through=0): - if is_else and isinstance(node, CallExpr): + if map is not None: + self.chk.push_type_map(map) + if is_else and context is not None and isinstance(node, CallExpr): # When calling a function on the else part, # we can face a generic function with multiple type vars. # When inferecing it, `context` might be used instead of real args. # Usually, we don't want that. # https://github.com/python/mypy/issues/11049 - context = None + if not is_subtype(self.accept(node), context, ignore_type_params=True): + context = None if map is None: # We still need to type check node, in case we want to # process it for isinstance checks later self.accept(node, type_context=context, allow_none_return=allow_none_return) return UninhabitedType() - self.chk.push_type_map(map) return self.accept(node, type_context=context, allow_none_return=allow_none_return) def visit_backquote_expr(self, e: BackquoteExpr) -> Type: diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 7b63f60addc1..7e4d7da819c4 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -24,84 +24,84 @@ # List of files that contain test case descriptions. typecheck_files = [ - 'check-basic.test', - 'check-union-or-syntax.test', - 'check-callable.test', - 'check-classes.test', - 'check-statements.test', - 'check-generics.test', - 'check-dynamic-typing.test', + # 'check-basic.test', + # 'check-union-or-syntax.test', + # 'check-callable.test', + # 'check-classes.test', + # 'check-statements.test', + # 'check-generics.test', + # 'check-dynamic-typing.test', 'check-inference.test', 'check-inference-context.test', - 'check-kwargs.test', - 'check-overloading.test', - 'check-type-checks.test', - 'check-abstract.test', - 'check-multiple-inheritance.test', - 'check-super.test', - 'check-modules.test', - 'check-typevar-values.test', - 'check-unsupported.test', - 'check-unreachable-code.test', - 'check-unions.test', - 'check-isinstance.test', - 'check-lists.test', - 'check-namedtuple.test', - 'check-narrowing.test', - 'check-typeddict.test', - 'check-type-aliases.test', - 'check-ignore.test', - 'check-type-promotion.test', - 'check-semanal-error.test', - 'check-flags.test', - 'check-incremental.test', - 'check-serialize.test', - 'check-bound.test', - 'check-optional.test', - 'check-fastparse.test', - 'check-warnings.test', - 'check-async-await.test', - 'check-newtype.test', - 'check-class-namedtuple.test', - 'check-selftype.test', - 'check-python2.test', - 'check-columns.test', - 'check-functions.test', - 'check-tuples.test', - 'check-expressions.test', - 'check-generic-subtyping.test', - 'check-varargs.test', - 'check-newsyntax.test', - 'check-protocols.test', - 'check-underscores.test', - 'check-classvar.test', - 'check-enum.test', - 'check-incomplete-fixture.test', - 'check-custom-plugin.test', - 'check-default-plugin.test', - 'check-attr.test', - 'check-ctypes.test', - 'check-dataclasses.test', - 'check-final.test', - 'check-redefine.test', - 'check-literal.test', - 'check-newsemanal.test', - 'check-inline-config.test', - 'check-reports.test', - 'check-errorcodes.test', - 'check-annotated.test', - 'check-parameter-specification.test', - 'check-generic-alias.test', - 'check-typeguard.test', - 'check-functools.test', - 'check-singledispatch.test', + # 'check-kwargs.test', + # 'check-overloading.test', + # 'check-type-checks.test', + # 'check-abstract.test', + # 'check-multiple-inheritance.test', + # 'check-super.test', + # 'check-modules.test', + # 'check-typevar-values.test', + # 'check-unsupported.test', + # 'check-unreachable-code.test', + # 'check-unions.test', + # 'check-isinstance.test', + # 'check-lists.test', + # 'check-namedtuple.test', + # 'check-narrowing.test', + # 'check-typeddict.test', + # 'check-type-aliases.test', + # 'check-ignore.test', + # 'check-type-promotion.test', + # 'check-semanal-error.test', + # 'check-flags.test', + # 'check-incremental.test', + # 'check-serialize.test', + # 'check-bound.test', + # 'check-optional.test', + # 'check-fastparse.test', + # 'check-warnings.test', + # 'check-async-await.test', + # 'check-newtype.test', + # 'check-class-namedtuple.test', + # 'check-selftype.test', + # 'check-python2.test', + # 'check-columns.test', + # 'check-functions.test', + # 'check-tuples.test', + # 'check-expressions.test', + # 'check-generic-subtyping.test', + # 'check-varargs.test', + # 'check-newsyntax.test', + # 'check-protocols.test', + # 'check-underscores.test', + # 'check-classvar.test', + # 'check-enum.test', + # 'check-incomplete-fixture.test', + # 'check-custom-plugin.test', + # 'check-default-plugin.test', + # 'check-attr.test', + # 'check-ctypes.test', + # 'check-dataclasses.test', + # 'check-final.test', + # 'check-redefine.test', + # 'check-literal.test', + # 'check-newsemanal.test', + # 'check-inline-config.test', + # 'check-reports.test', + # 'check-errorcodes.test', + # 'check-annotated.test', + # 'check-parameter-specification.test', + # 'check-generic-alias.test', + # 'check-typeguard.test', + # 'check-functools.test', + # 'check-singledispatch.test', ] # Tests that use Python 3.8-only AST features (like expression-scoped ignores): -if sys.version_info >= (3, 8): - typecheck_files.append('check-python38.test') -if sys.version_info >= (3, 9): - typecheck_files.append('check-python39.test') +# if sys.version_info >= (3, 8): +# typecheck_files.append('check-python38.test') +# if sys.version_info >= (3, 9): +# typecheck_files.append('check-python39.test') # Special tests for platforms with case-insensitive filesystems. if sys.platform in ('darwin', 'win32'): diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 935f9de93b47..f4e399196bcc 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2246,6 +2246,30 @@ a = set() if f() else {0} a() # E: "Set[int]" not callable [builtins fixtures/set.pyi] +[case testUnificationEmptyCustomSetLeft] +from typing import Set, TypeVar +T = TypeVar('T') +class customset(Set[T]): pass +def f(): pass +a = customset() if f() else {1} +a() # E: "Set[int]" not callable +[builtins fixtures/set.pyi] + +[case testUnificationEmptySetRight] +def f(): pass +a = {0} if f() else set() +a() # E: "Set[int]" not callable +[builtins fixtures/set.pyi] + +[case testUnificationEmptyCustomSetRight] +from typing import Set, TypeVar +T = TypeVar('T') +class customset(Set[T]): pass +def f(): pass +a = {0} if f() else customset() +a() # E: "Set[int]" not callable +[builtins fixtures/set.pyi] + [case testUnificationEmptyDictLeft] def f(): pass a = {} if f() else {0: 0} @@ -2300,6 +2324,28 @@ reveal_type(foo("s", 1) if x else 1) # N: Revealed type is "Union[builtins.str* reveal_type(foo("s", 1) if x else "a") # N: Revealed type is "Union[builtins.str*, builtins.int*]" [builtins fixtures/bool.pyi] +[case testConditionalInferenceSelfNarrowingRight] +from typing import Optional + +class C: + x: Optional[int] + def check(self) -> Optional[int]: + return None if self.x is None else self.x.conjugate() + +reveal_type(C().check()) # N: Revealed type is "Union[builtins.int, None]" +[builtins fixtures/bool.pyi] + +[case testConditionalInferenceSelfNarrowingLeft] +from typing import Optional + +class C: + x: Optional[int] + def check(self) -> Optional[int]: + return self.x.conjugate() if self.x is not None else None + +reveal_type(C().check()) # N: Revealed type is "Union[builtins.int, None]" +[builtins fixtures/bool.pyi] + [case testMisguidedSetItem] from typing import Generic, Sequence, TypeVar T = TypeVar('T') diff --git a/test-data/unit/fixtures/bool.pyi b/test-data/unit/fixtures/bool.pyi index ca2564dabafd..a82a45f06e6a 100644 --- a/test-data/unit/fixtures/bool.pyi +++ b/test-data/unit/fixtures/bool.pyi @@ -10,7 +10,8 @@ class object: class type: pass class tuple(Generic[T]): pass class function: pass -class int: pass +class int: + def conjugate(self) -> int: pass class bool(int): pass class float: pass class str: pass From 2920f334bdd89a802da5fd2ec4241437514d00fc Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 17 Sep 2021 19:43:19 +0300 Subject: [PATCH 3/5] Fixes tests --- mypy/test/testcheck.py | 146 ++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 7e4d7da819c4..7b63f60addc1 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -24,84 +24,84 @@ # List of files that contain test case descriptions. typecheck_files = [ - # 'check-basic.test', - # 'check-union-or-syntax.test', - # 'check-callable.test', - # 'check-classes.test', - # 'check-statements.test', - # 'check-generics.test', - # 'check-dynamic-typing.test', + 'check-basic.test', + 'check-union-or-syntax.test', + 'check-callable.test', + 'check-classes.test', + 'check-statements.test', + 'check-generics.test', + 'check-dynamic-typing.test', 'check-inference.test', 'check-inference-context.test', - # 'check-kwargs.test', - # 'check-overloading.test', - # 'check-type-checks.test', - # 'check-abstract.test', - # 'check-multiple-inheritance.test', - # 'check-super.test', - # 'check-modules.test', - # 'check-typevar-values.test', - # 'check-unsupported.test', - # 'check-unreachable-code.test', - # 'check-unions.test', - # 'check-isinstance.test', - # 'check-lists.test', - # 'check-namedtuple.test', - # 'check-narrowing.test', - # 'check-typeddict.test', - # 'check-type-aliases.test', - # 'check-ignore.test', - # 'check-type-promotion.test', - # 'check-semanal-error.test', - # 'check-flags.test', - # 'check-incremental.test', - # 'check-serialize.test', - # 'check-bound.test', - # 'check-optional.test', - # 'check-fastparse.test', - # 'check-warnings.test', - # 'check-async-await.test', - # 'check-newtype.test', - # 'check-class-namedtuple.test', - # 'check-selftype.test', - # 'check-python2.test', - # 'check-columns.test', - # 'check-functions.test', - # 'check-tuples.test', - # 'check-expressions.test', - # 'check-generic-subtyping.test', - # 'check-varargs.test', - # 'check-newsyntax.test', - # 'check-protocols.test', - # 'check-underscores.test', - # 'check-classvar.test', - # 'check-enum.test', - # 'check-incomplete-fixture.test', - # 'check-custom-plugin.test', - # 'check-default-plugin.test', - # 'check-attr.test', - # 'check-ctypes.test', - # 'check-dataclasses.test', - # 'check-final.test', - # 'check-redefine.test', - # 'check-literal.test', - # 'check-newsemanal.test', - # 'check-inline-config.test', - # 'check-reports.test', - # 'check-errorcodes.test', - # 'check-annotated.test', - # 'check-parameter-specification.test', - # 'check-generic-alias.test', - # 'check-typeguard.test', - # 'check-functools.test', - # 'check-singledispatch.test', + 'check-kwargs.test', + 'check-overloading.test', + 'check-type-checks.test', + 'check-abstract.test', + 'check-multiple-inheritance.test', + 'check-super.test', + 'check-modules.test', + 'check-typevar-values.test', + 'check-unsupported.test', + 'check-unreachable-code.test', + 'check-unions.test', + 'check-isinstance.test', + 'check-lists.test', + 'check-namedtuple.test', + 'check-narrowing.test', + 'check-typeddict.test', + 'check-type-aliases.test', + 'check-ignore.test', + 'check-type-promotion.test', + 'check-semanal-error.test', + 'check-flags.test', + 'check-incremental.test', + 'check-serialize.test', + 'check-bound.test', + 'check-optional.test', + 'check-fastparse.test', + 'check-warnings.test', + 'check-async-await.test', + 'check-newtype.test', + 'check-class-namedtuple.test', + 'check-selftype.test', + 'check-python2.test', + 'check-columns.test', + 'check-functions.test', + 'check-tuples.test', + 'check-expressions.test', + 'check-generic-subtyping.test', + 'check-varargs.test', + 'check-newsyntax.test', + 'check-protocols.test', + 'check-underscores.test', + 'check-classvar.test', + 'check-enum.test', + 'check-incomplete-fixture.test', + 'check-custom-plugin.test', + 'check-default-plugin.test', + 'check-attr.test', + 'check-ctypes.test', + 'check-dataclasses.test', + 'check-final.test', + 'check-redefine.test', + 'check-literal.test', + 'check-newsemanal.test', + 'check-inline-config.test', + 'check-reports.test', + 'check-errorcodes.test', + 'check-annotated.test', + 'check-parameter-specification.test', + 'check-generic-alias.test', + 'check-typeguard.test', + 'check-functools.test', + 'check-singledispatch.test', ] # Tests that use Python 3.8-only AST features (like expression-scoped ignores): -# if sys.version_info >= (3, 8): -# typecheck_files.append('check-python38.test') -# if sys.version_info >= (3, 9): -# typecheck_files.append('check-python39.test') +if sys.version_info >= (3, 8): + typecheck_files.append('check-python38.test') +if sys.version_info >= (3, 9): + typecheck_files.append('check-python39.test') # Special tests for platforms with case-insensitive filesystems. if sys.platform in ('darwin', 'win32'): From cba02230fc2620032d948b32b71950d99e1e0760 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 17 Sep 2021 19:44:11 +0300 Subject: [PATCH 4/5] Fixes tests --- test-data/unit/check-inference.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index f4e399196bcc..8de828ae6c44 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2246,6 +2246,12 @@ a = set() if f() else {0} a() # E: "Set[int]" not callable [builtins fixtures/set.pyi] +[case testUnificationEmptySetRight] +def f(): pass +a = {0} if f() else set() +a() # E: "Set[int]" not callable +[builtins fixtures/set.pyi] + [case testUnificationEmptyCustomSetLeft] from typing import Set, TypeVar T = TypeVar('T') @@ -2255,12 +2261,6 @@ a = customset() if f() else {1} a() # E: "Set[int]" not callable [builtins fixtures/set.pyi] -[case testUnificationEmptySetRight] -def f(): pass -a = {0} if f() else set() -a() # E: "Set[int]" not callable -[builtins fixtures/set.pyi] - [case testUnificationEmptyCustomSetRight] from typing import Set, TypeVar T = TypeVar('T') From cffe75a05c661e732c13f6606c0b739f06f06989 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Sep 2021 17:38:11 +0300 Subject: [PATCH 5/5] Adds `with self.msg.disable_errors():` --- mypy/checkexpr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b499f9a87b6e..62370eee4736 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3884,7 +3884,9 @@ def analyze_cond_branch(self, map: Optional[Dict[Expression, Type]], # When inferecing it, `context` might be used instead of real args. # Usually, we don't want that. # https://github.com/python/mypy/issues/11049 - if not is_subtype(self.accept(node), context, ignore_type_params=True): + with self.msg.disable_errors(): + call_type = self.accept(node) + if not is_subtype(call_type, context, ignore_type_params=True): context = None if map is None: