From e1d19bcff5e0b83b379993c979a844ebd701b995 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Fri, 15 Nov 2024 00:53:58 +0800 Subject: [PATCH 1/5] feat: narrow capture pattern types during inference --- mypy/checker.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1bee348bc2526..373ec8a2e06fa 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -35,7 +35,7 @@ analyze_member_access, type_object_type, ) -from mypy.checkpattern import PatternChecker +from mypy.checkpattern import PatternChecker, PatternType from mypy.constraints import SUPERTYPE_OF from mypy.erasetype import erase_type, erase_typevars, remove_instance_last_known_values from mypy.errorcodes import TYPE_VAR, UNUSED_AWAITABLE, UNUSED_COROUTINE, ErrorCode @@ -5296,7 +5296,13 @@ def visit_match_stmt(self, s: MatchStmt) -> None: # capture variable may depend on multiple patterns (it # will be a union of all capture types). This pass ignores # guard expressions. - pattern_types = [self.pattern_checker.accept(p, subject_type) for p in s.patterns] + pattern_types: list[PatternType] = [] + type_context = subject_type + for p in s.patterns: + pattern_type = self.pattern_checker.accept(p, type_context) + pattern_types.append(pattern_type) + type_context = get_proper_type(pattern_type.rest_type) + type_maps: list[TypeMap] = [t.captures for t in pattern_types] inferred_types = self.infer_variable_types_from_type_maps(type_maps) From 78cd7fe7dff2bde50c07ae288231e19c7316c127 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Fri, 15 Nov 2024 00:55:03 +0800 Subject: [PATCH 2/5] test: remove reuse of capture pattern `r2` --- test-data/unit/check-python310.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 435fbde3e2ae4..5fd0ffb4c2923 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1466,7 +1466,7 @@ m3: Tuple[Union[int, str]] match m3: case (1,): reveal_type(m3) # N: Revealed type is "Tuple[Literal[1]]" - case r2: + case r3: reveal_type(m3) # N: Revealed type is "Tuple[Union[builtins.int, builtins.str]]" m4: Tuple[Literal[1], int] From b23b85b843aacc80184cad2893d3d6f628b48e39 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Mon, 18 Nov 2024 23:23:22 +0800 Subject: [PATCH 3/5] fix: only narrow capture variable type context if not uninhabited --- mypy/checker.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 373ec8a2e06fa..3dfe493484cce 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5301,7 +5301,10 @@ def visit_match_stmt(self, s: MatchStmt) -> None: for p in s.patterns: pattern_type = self.pattern_checker.accept(p, type_context) pattern_types.append(pattern_type) - type_context = get_proper_type(pattern_type.rest_type) + new_type_context = get_proper_type(pattern_type.rest_type) + if isinstance(new_type_context, UninhabitedType): + continue + type_context = new_type_context type_maps: list[TypeMap] = [t.captures for t in pattern_types] inferred_types = self.infer_variable_types_from_type_maps(type_maps) From a017ea540e9e14f0b8014b5ab59e5897a7848110 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Mon, 18 Nov 2024 23:23:46 +0800 Subject: [PATCH 4/5] test: add unit test for capture pattern narrowing --- test-data/unit/check-python310.test | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 5fd0ffb4c2923..398c1bfdbf455 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -8,6 +8,16 @@ match m: case a: reveal_type(a) # N: Revealed type is "__main__.A" +[case testMatchCapturePatternNarrows] +x: int | None +y: int + +match x: + case None: + pass + case y: + reveal_type(y) # N: Revealed type is "builtins.int" + -- Literal Pattern -- [case testMatchLiteralPatternNarrows] From 4f2e9b41ed6af6885871c39500573a82bf95cd53 Mon Sep 17 00:00:00 2001 From: Shen Yi Hong Date: Tue, 19 Nov 2024 00:56:00 +0800 Subject: [PATCH 5/5] fix: do not narrow if `UninhabitedType` in union --- mypy/checker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 3dfe493484cce..b818b40c0aaa0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5304,6 +5304,10 @@ def visit_match_stmt(self, s: MatchStmt) -> None: new_type_context = get_proper_type(pattern_type.rest_type) if isinstance(new_type_context, UninhabitedType): continue + if isinstance(new_type_context, UnionType) and any( + isinstance(get_proper_type(t), UninhabitedType) for t in new_type_context.items + ): + continue type_context = new_type_context type_maps: list[TypeMap] = [t.captures for t in pattern_types]