Repository: python/mypy (Python)

Issue:
@overload cannot be used with conditional "if TYPE_CHECKING"
**Bug Report**

The handling of `@overload` decorators happens in (code) blocks only, which makes it not find the implementation when using:
```python
from typing import overload
from typing import TYPE_CHECKING
from typing import Union


class K:
    if TYPE_CHECKING:
        @overload
        def foo(self, arg: int) -> int:
            ...

        @overload
        def foo(self, arg: str) -> str:
            ...

    def foo(self, arg: Union[int, str]) -> Union[int, str]:
        ...
```

```
t-overload.py:8: error: An overloaded function outside a stub file must have an implementation  [misc]
t-overload.py:16: error: Name 'foo' already defined on line 8  [no-redef]
Found 2 errors in 1 file (checked 1 source file)
```

I think it would be nice / feasible to merge `if TYPE_CHECKING` blocks maybe, so that the parser would handle it together?

mypy 0.800+dev.6e99a2db100d794b1bf900746470527cd03fce16


Hints:
I'm curious, why would you like an `if TYPE_CHECKING` block shielding overloads? They are ignored by the runtime anyways.
> I'm curious, why would you like an `if TYPE_CHECKING` block shielding overloads? They are ignored by the runtime anyways.

While it's just a dummy decorator I'd like to keep typing separate from runtime altogether.
Apart from that it just confused me initially until I've looked into how mypy parses/handles it, and was surprised to see that `if` blocks are considered to be separate in this regard, i.e. it looked like something that could/should get improved/fixed in mypy anyway.
I think this applies to my present issue as well.  I am exploring merging trio-stubs (from trio-typing) into Trio itself and the hints for `open_process()` and `run_process()` are platform dependent.

https://github.com/python-trio/trio-typing/blob/f32f17b0f242daf2d42407f383ca581d64b6c299/trio-stubs/__init__.pyi#L567-L682
https://github.com/python-trio/trio/blob/d890affa8fed3a49b616a8fd218f4fb928bd3002/trio/_subprocess.py#L279
https://github.com/python-trio/trio/blob/d890affa8fed3a49b616a8fd218f4fb928bd3002/trio/_subprocess.py#L419-L428
For your use case you could just put the whole stub in an `if TYPE_CHECKING` and add a dummy implementation (`def whatever(*args, **kwargs): ...`) to each `sys.platform` branch. And then put the real implementation in the `else` of the `if TYPE_CHECKING`
Thanks for the workaround.  I'll keep subscribed here in case there's a full fix.  `:]`

Patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -44,7 +44,7 @@
 from mypy import message_registry, errorcodes as codes
 from mypy.errors import Errors
 from mypy.options import Options
-from mypy.reachability import mark_block_unreachable
+from mypy.reachability import infer_reachability_of_if_statement, mark_block_unreachable
 from mypy.util import bytes_to_human_readable_repr
 
 try:
@@ -344,9 +344,19 @@ def fail(self,
              msg: str,
              line: int,
              column: int,
-             blocker: bool = True) -> None:
+             blocker: bool = True,
+             code: codes.ErrorCode = codes.SYNTAX) -> None:
         if blocker or not self.options.ignore_errors:
-            self.errors.report(line, column, msg, blocker=blocker, code=codes.SYNTAX)
+            self.errors.report(line, column, msg, blocker=blocker, code=code)
+
+    def fail_merge_overload(self, node: IfStmt) -> None:
+        self.fail(
+            "Condition can't be inferred, unable to merge overloads",
+            line=node.line,
+            column=node.column,
+            blocker=False,
+            code=codes.MISC,
+        )
 
     def visit(self, node: Optional[AST]) -> Any:
         if node is None:
@@ -476,12 +486,93 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
         ret: List[Statement] = []
         current_overload: List[OverloadPart] = []
         current_overload_name: Optional[str] = None
+        last_if_stmt: Optional[IfStmt] = None
+        last_if_overload: Optional[Union[Decorator, FuncDef, OverloadedFuncDef]] = None
+        last_if_stmt_overload_name: Optional[str] = None
+        last_if_unknown_truth_value: Optional[IfStmt] = None
+        skipped_if_stmts: List[IfStmt] = []
         for stmt in stmts:
+            if_overload_name: Optional[str] = None
+            if_block_with_overload: Optional[Block] = None
+            if_unknown_truth_value: Optional[IfStmt] = None
+            if (
+                isinstance(stmt, IfStmt)
+                and len(stmt.body[0].body) == 1
+                and (
+                    isinstance(stmt.body[0].body[0], (Decorator, OverloadedFuncDef))
+                    or current_overload_name is not None
+                    and isinstance(stmt.body[0].body[0], FuncDef)
+                )
+            ):
+                # Check IfStmt block to determine if function overloads can be merged
+                if_overload_name = self._check_ifstmt_for_overloads(stmt)
+                if if_overload_name is not None:
+                    if_block_with_overload, if_unknown_truth_value = \
+                        self._get_executable_if_block_with_overloads(stmt)
+
             if (current_overload_name is not None
                     and isinstance(stmt, (Decorator, FuncDef))
                     and stmt.name == current_overload_name):
+                if last_if_stmt is not None:
+                    skipped_if_stmts.append(last_if_stmt)
+                if last_if_overload is not None:
+                    # Last stmt was an IfStmt with same overload name
+                    # Add overloads to current_overload
+                    if isinstance(last_if_overload, OverloadedFuncDef):
+                        current_overload.extend(last_if_overload.items)
+                    else:
+                        current_overload.append(last_if_overload)
+                    last_if_stmt, last_if_overload = None, None
+                if last_if_unknown_truth_value:
+                    self.fail_merge_overload(last_if_unknown_truth_value)
+                    last_if_unknown_truth_value = None
                 current_overload.append(stmt)
+            elif (
+                current_overload_name is not None
+                and isinstance(stmt, IfStmt)
+                and if_overload_name == current_overload_name
+            ):
+                # IfStmt only contains stmts relevant to current_overload.
+                # Check if stmts are reachable and add them to current_overload,
+                # otherwise skip IfStmt to allow subsequent overload
+                # or function definitions.
+                skipped_if_stmts.append(stmt)
+                if if_block_with_overload is None:
+                    if if_unknown_truth_value is not None:
+                        self.fail_merge_overload(if_unknown_truth_value)
+                    continue
+                if last_if_overload is not None:
+                    # Last stmt was an IfStmt with same overload name
+                    # Add overloads to current_overload
+                    if isinstance(last_if_overload, OverloadedFuncDef):
+                        current_overload.extend(last_if_overload.items)
+                    else:
+                        current_overload.append(last_if_overload)
+                    last_if_stmt, last_if_overload = None, None
+                if isinstance(if_block_with_overload.body[0], OverloadedFuncDef):
+                    current_overload.extend(if_block_with_overload.body[0].items)
+                else:
+                    current_overload.append(
+                        cast(Union[Decorator, FuncDef], if_block_with_overload.body[0])
+                    )
             else:
+                if last_if_stmt is not None:
+                    ret.append(last_if_stmt)
+                    last_if_stmt_overload_name = current_overload_name
+                    last_if_stmt, last_if_overload = None, None
+                    last_if_unknown_truth_value = None
+
+                if current_overload and current_overload_name == last_if_stmt_overload_name:
+                    # Remove last stmt (IfStmt) from ret if the overload names matched
+                    # Only happens if no executable block had been found in IfStmt
+                    skipped_if_stmts.append(cast(IfStmt, ret.pop()))
+                if current_overload and skipped_if_stmts:
+                    # Add bare IfStmt (without overloads) to ret
+                    # Required for mypy to be able to still check conditions
+                    for if_stmt in skipped_if_stmts:
+                        self._strip_contents_from_if_stmt(if_stmt)
+                        ret.append(if_stmt)
+                    skipped_if_stmts = []
                 if len(current_overload) == 1:
                     ret.append(current_overload[0])
                 elif len(current_overload) > 1:
@@ -495,17 +586,119 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
                 if isinstance(stmt, Decorator) and not unnamed_function(stmt.name):
                     current_overload = [stmt]
                     current_overload_name = stmt.name
+                elif (
+                    isinstance(stmt, IfStmt)
+                    and if_overload_name is not None
+                ):
+                    current_overload = []
+                    current_overload_name = if_overload_name
+                    last_if_stmt = stmt
+                    last_if_stmt_overload_name = None
+                    if if_block_with_overload is not None:
+                        last_if_overload = cast(
+                            Union[Decorator, FuncDef, OverloadedFuncDef],
+                            if_block_with_overload.body[0]
+                        )
+                    last_if_unknown_truth_value = if_unknown_truth_value
                 else:
                     current_overload = []
                     current_overload_name = None
                     ret.append(stmt)
 
+        if current_overload and skipped_if_stmts:
+            # Add bare IfStmt (without overloads) to ret
+            # Required for mypy to be able to still check conditions
+            for if_stmt in skipped_if_stmts:
+                self._strip_contents_from_if_stmt(if_stmt)
+                ret.append(if_stmt)
         if len(current_overload) == 1:
             ret.append(current_overload[0])
         elif len(current_overload) > 1:
             ret.append(OverloadedFuncDef(current_overload))
+        elif last_if_stmt is not None:
+            ret.append(last_if_stmt)
         return ret
 
+    def _check_ifstmt_for_overloads(self, stmt: IfStmt) -> Optional[str]:
+        """Check if IfStmt contains only overloads with the same name.
+        Return overload_name if found, None otherwise.
+        """
+        # Check that block only contains a single Decorator, FuncDef, or OverloadedFuncDef.
+        # Multiple overloads have already been merged as OverloadedFuncDef.
+        if not (
+            len(stmt.body[0].body) == 1
+            and isinstance(stmt.body[0].body[0], (Decorator, FuncDef, OverloadedFuncDef))
+        ):
+            return None
+
+        overload_name = stmt.body[0].body[0].name
+        if stmt.else_body is None:
+            return overload_name
+
+        if isinstance(stmt.else_body, Block) and len(stmt.else_body.body) == 1:
+            # For elif: else_body contains an IfStmt itself -> do a recursive check.
+            if (
+                isinstance(stmt.else_body.body[0], (Decorator, FuncDef, OverloadedFuncDef))
+                and stmt.else_body.body[0].name == overload_name
+            ):
+                return overload_name
+            if (
+                isinstance(stmt.else_body.body[0], IfStmt)
+                and self._check_ifstmt_for_overloads(stmt.else_body.body[0]) == overload_name
+            ):
+                return overload_name
+
+        return None
+
+    def _get_executable_if_block_with_overloads(
+        self, stmt: IfStmt
+    ) -> Tuple[Optional[Block], Optional[IfStmt]]:
+        """Return block from IfStmt that will get executed.
+
+        Return
+            0 -> A block if sure that alternative blocks are unreachable.
+            1 -> An IfStmt if the reachability of it can't be inferred,
+                 i.e. the truth value is unknown.
+        """
+        infer_reachability_of_if_statement(stmt, self.options)
+        if (
+            stmt.else_body is None
+            and stmt.body[0].is_unreachable is True
+        ):
+            # always False condition with no else
+            return None, None
+        if (
+            stmt.else_body is None
+            or stmt.body[0].is_unreachable is False
+            and stmt.else_body.is_unreachable is False
+        ):
+            # The truth value is unknown, thus not conclusive
+            return None, stmt
+        if stmt.else_body.is_unreachable is True:
+            # else_body will be set unreachable if condition is always True
+            return stmt.body[0], None
+        if stmt.body[0].is_unreachable is True:
+            # body will be set unreachable if condition is always False
+            # else_body can contain an IfStmt itself (for elif) -> do a recursive check
+            if isinstance(stmt.else_body.body[0], IfStmt):
+                return self._get_executable_if_block_with_overloads(stmt.else_body.body[0])
+            return stmt.else_body, None
+        return None, stmt
+
+    def _strip_contents_from_if_stmt(self, stmt: IfStmt) -> None:
+        """Remove contents from IfStmt.
+
+        Needed to still be able to check the conditions after the contents
+        have been merged with the surrounding function overloads.
+        """
+        if len(stmt.body) == 1:
+            stmt.body[0].body = []
+        if stmt.else_body and len(stmt.else_body.body) == 1:
+            if isinstance(stmt.else_body.body[0], IfStmt):
+                self._strip_contents_from_if_stmt(stmt.else_body.body[0])
+            else:
+                stmt.else_body.body = []
+
     def in_method_scope(self) -> bool:
         return self.class_and_function_stack[-2:] == ['C', 'F']
 


Test patch:
diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test
--- a/test-data/unit/check-overloading.test
+++ b/test-data/unit/check-overloading.test
@@ -5421,3 +5421,884 @@ def f_f(arg: str) -> None: ...
 @Bad2()  # E: "Bad2" not callable
 def f_f(arg): ...
 [builtins fixtures/dict.pyi]
+
+
+[case testOverloadIfBasic]
+# flags: --always-true True --always-false False
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# Test basic overload merging
+# -----
+
+@overload
+def f1(g: A) -> A: ...
+if True:
+    @overload
+    def f1(g: B) -> B: ...
+def f1(g): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+
+@overload
+def f2(g: A) -> A: ...
+@overload
+def f2(g: B) -> B: ...
+if False:
+    @overload
+    def f2(g: C) -> C: ...
+def f2(g): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(C()))  # E: No overload variant of "f2" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f2(g: A) -> A \
+    # N:     def f2(g: B) -> B \
+    # N: Revealed type is "Any"
+
+@overload
+def f3(g: A) -> A: ...
+@overload
+def f3(g: B) -> B: ...
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f3(g: C) -> C: ...
+def f3(g): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f3(C()))  # E: No overload variant of "f3" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f3(g: A) -> A \
+    # N:     def f3(g: B) -> B \
+    # N: Revealed type is "Any"
+
+if True:
+    @overload
+    def f4(g: A) -> A: ...
+if True:
+    @overload
+    def f4(g: B) -> B: ...
+@overload
+def f4(g: C) -> C: ...
+def f4(g): ...
+reveal_type(f4(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f4(B()))  # N: Revealed type is "__main__.B"
+reveal_type(f4(C()))  # N: Revealed type is "__main__.C"
+
+if True:
+    @overload
+    def f5(g: A) -> A: ...
+@overload
+def f5(g: B) -> B: ...
+if True:
+    @overload
+    def f5(g: C) -> C: ...
+@overload
+def f5(g: D) -> D: ...
+def f5(g): ...
+reveal_type(f5(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f5(B()))  # N: Revealed type is "__main__.B"
+reveal_type(f5(C()))  # N: Revealed type is "__main__.C"
+reveal_type(f5(D()))  # N: Revealed type is "__main__.D"
+
+[case testOverloadIfSysVersion]
+# flags: --python-version 3.9
+from typing import overload
+import sys
+
+class A: ...
+class B: ...
+class C: ...
+
+# -----
+# "Real" world example
+# Test overload merging for sys.version_info
+# -----
+
+@overload
+def f1(g: A) -> A: ...
+if sys.version_info >= (3, 9):
+    @overload
+    def f1(g: B) -> B: ...
+def f1(g): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+
+@overload
+def f2(g: A) -> A: ...
+@overload
+def f2(g: B) -> B: ...
+if sys.version_info >= (3, 10):
+    @overload
+    def f2(g: C) -> C: ...
+def f2(g): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(C()))  # E: No overload variant of "f2" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f2(g: A) -> A \
+    # N:     def f2(g: B) -> B \
+    # N: Revealed type is "Any"
+[builtins fixtures/ops.pyi]
+
+[case testOverloadIfMerging]
+# flags: --always-true True
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+
+# -----
+# Test overload merging
+# -----
+
+@overload
+def f1(g: A) -> A: ...
+if True:
+    # Some comment
+    @overload
+    def f1(g: B) -> B: ...
+def f1(g): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+
+@overload
+def f2(g: A) -> A: ...
+if True:
+    @overload
+    def f2(g: bytes) -> B: ...
+    @overload
+    def f2(g: B) -> C: ...
+def f2(g): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(B()))  # N: Revealed type is "__main__.C"
+
+@overload
+def f3(g: A) -> A: ...
+@overload
+def f3(g: B) -> B: ...
+if True:
+    def f3(g): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f3(B()))  # N: Revealed type is "__main__.B"
+
+if True:
+    @overload
+    def f4(g: A) -> A: ...
+@overload
+def f4(g: B) -> B: ...
+def f4(g): ...
+reveal_type(f4(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f4(B()))  # N: Revealed type is "__main__.B"
+
+if True:
+    # Some comment
+    @overload
+    def f5(g: A) -> A: ...
+    @overload
+    def f5(g: B) -> B: ...
+def f5(g): ...
+reveal_type(f5(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f5(B()))  # N: Revealed type is "__main__.B"
+
+[case testOverloadIfNotMerging]
+# flags: --always-true True
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+
+# -----
+# Don't merge if IfStmt contains nodes other than overloads
+# -----
+
+@overload  # E: An overloaded function outside a stub file must have an implementation
+def f1(g: A) -> A: ...
+@overload
+def f1(g: B) -> B: ...
+if True:
+    @overload  # E: Name "f1" already defined on line 12 \
+               # E: Single overload definition, multiple required
+    def f1(g: C) -> C: ...
+    pass  # Some other action
+def f1(g): ...  # E: Name "f1" already defined on line 12
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(C()))  # E: No overload variant of "f1" matches argument type "C" \
+                          # N: Possible overload variants: \
+                          # N:     def f1(g: A) -> A \
+                          # N:     def f1(g: B) -> B \
+                          # N: Revealed type is "Any"
+
+if True:
+    pass  # Some other action
+    @overload  # E: Single overload definition, multiple required
+    def f2(g: A) -> A: ...
+@overload  # E: Name "f2" already defined on line 26
+def f2(g: B) -> B: ...
+@overload
+def f2(g: C) -> C: ...
+def f2(g): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(C()))  # N: Revealed type is "__main__.A" \
+    # E: Argument 1 to "f2" has incompatible type "C"; expected "A"
+
+[case testOverloadIfOldStyle]
+# flags: --always-false var_false --always-true var_true
+from typing import overload
+
+class A: ...
+class B: ...
+
+# -----
+# Test old style to make sure it still works
+# -----
+
+var_true = True
+var_false = False
+
+if var_false:
+    @overload
+    def f1(g: A) -> A: ...
+    @overload
+    def f1(g: B) -> B: ...
+    def f1(g): ...
+elif var_true:
+    @overload
+    def f1(g: A) -> A: ...
+    @overload
+    def f1(g: B) -> B: ...
+    def f1(g): ...
+else:
+    @overload
+    def f1(g: A) -> A: ...
+    @overload
+    def f1(g: B) -> B: ...
+    def f1(g): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+
+[case testOverloadIfElse]
+# flags: --always-true True --always-false False
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# Match the first always-true block
+# -----
+
+@overload
+def f1(x: A) -> A: ...
+if True:
+    @overload
+    def f1(x: B) -> B: ...
+elif False:
+    @overload
+    def f1(x: C) -> C: ...
+else:
+    @overload
+    def f1(x: D) -> D: ...
+def f1(x): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+reveal_type(f1(C()))  # E: No overload variant of "f1" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f1(x: A) -> A \
+    # N:     def f1(x: B) -> B \
+    # N: Revealed type is "Any"
+
+@overload
+def f2(x: A) -> A: ...
+if False:
+    @overload
+    def f2(x: B) -> B: ...
+elif True:
+    @overload
+    def f2(x: C) -> C: ...
+else:
+    @overload
+    def f2(x: D) -> D: ...
+def f2(x): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(B()))  # E: No overload variant of "f2" matches argument type "B" \
+    # N: Possible overload variants: \
+    # N:     def f2(x: A) -> A \
+    # N:     def f2(x: C) -> C \
+    # N: Revealed type is "Any"
+reveal_type(f2(C()))  # N: Revealed type is "__main__.C"
+
+@overload
+def f3(x: A) -> A: ...
+if False:
+    @overload
+    def f3(x: B) -> B: ...
+elif False:
+    @overload
+    def f3(x: C) -> C: ...
+else:
+    @overload
+    def f3(x: D) -> D: ...
+def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f3(C()))  # E: No overload variant of "f3" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f3(x: A) -> A \
+    # N:     def f3(x: D) -> D \
+    # N: Revealed type is "Any"
+reveal_type(f3(D()))  # N: Revealed type is "__main__.D"
+
+[case testOverloadIfElse2]
+# flags: --always-true True
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# Match the first always-true block
+# Don't merge overloads if can't be certain about execution of block
+# -----
+
+@overload
+def f1(x: A) -> A: ...
+if True:
+    @overload
+    def f1(x: B) -> B: ...
+else:
+    @overload
+    def f1(x: D) -> D: ...
+def f1(x): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+reveal_type(f1(D()))  # E: No overload variant of "f1" matches argument type "D" \
+    # N: Possible overload variants: \
+    # N:     def f1(x: A) -> A \
+    # N:     def f1(x: B) -> B \
+    # N: Revealed type is "Any"
+
+@overload
+def f2(x: A) -> A: ...
+if True:
+    @overload
+    def f2(x: B) -> B: ...
+elif maybe_true:
+    @overload
+    def f2(x: C) -> C: ...
+else:
+    @overload
+    def f2(x: D) -> D: ...
+def f2(x): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(B()))  # N: Revealed type is "__main__.B"
+reveal_type(f2(C()))  # E: No overload variant of "f2" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f2(x: A) -> A \
+    # N:     def f2(x: B) -> B \
+    # N: Revealed type is "Any"
+
+@overload  # E: Single overload definition, multiple required
+def f3(x: A) -> A: ...
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f3(x: B) -> B: ...
+elif True:
+    @overload
+    def f3(x: C) -> C: ...
+else:
+    @overload
+    def f3(x: D) -> D: ...
+def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f3(B()))  # E: No overload variant of "f3" matches argument type "B" \
+    # N: Possible overload variant: \
+    # N:     def f3(x: A) -> A \
+    # N: Revealed type is "Any"
+
+@overload  # E: Single overload definition, multiple required
+def f4(x: A) -> A: ...
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f4(x: B) -> B: ...
+else:
+    @overload
+    def f4(x: D) -> D: ...
+def f4(x): ...
+reveal_type(f4(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f4(B()))  # E: No overload variant of "f4" matches argument type "B" \
+    # N: Possible overload variant: \
+    # N:     def f4(x: A) -> A \
+    # N: Revealed type is "Any"
+
+[case testOverloadIfElse3]
+# flags: --always-false False
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+class E: ...
+
+# -----
+# Match the first always-true block
+# Don't merge overloads if can't be certain about execution of block
+# -----
+
+@overload
+def f1(x: A) -> A: ...
+if False:
+    @overload
+    def f1(x: B) -> B: ...
+else:
+    @overload
+    def f1(x: D) -> D: ...
+def f1(x): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # E: No overload variant of "f1" matches argument type "B" \
+    # N: Possible overload variants: \
+    # N:     def f1(x: A) -> A \
+    # N:     def f1(x: D) -> D \
+    # N: Revealed type is "Any"
+reveal_type(f1(D()))  # N: Revealed type is "__main__.D"
+
+@overload  # E: Single overload definition, multiple required
+def f2(x: A) -> A: ...
+if False:
+    @overload
+    def f2(x: B) -> B: ...
+elif maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                  # E: Name "maybe_true" is not defined
+    @overload
+    def f2(x: C) -> C: ...
+else:
+    @overload
+    def f2(x: D) -> D: ...
+def f2(x): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(C()))  # E: No overload variant of "f2" matches argument type "C" \
+    # N: Possible overload variant: \
+    # N:     def f2(x: A) -> A \
+    # N: Revealed type is "Any"
+
+@overload  # E: Single overload definition, multiple required
+def f3(x: A) -> A: ...
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f3(x: B) -> B: ...
+elif False:
+    @overload
+    def f3(x: C) -> C: ...
+else:
+    @overload
+    def f3(x: D) -> D: ...
+def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f3(B()))  # E: No overload variant of "f3" matches argument type "B" \
+    # N: Possible overload variant: \
+    # N:     def f3(x: A) -> A \
+    # N: Revealed type is "Any"
+
+def g(bool_var: bool) -> None:
+    @overload
+    def f4(x: A) -> A: ...
+    if bool_var:  # E: Condition cannot be inferred, unable to merge overloads
+        @overload
+        def f4(x: B) -> B: ...
+    elif maybe_true:  # E: Name "maybe_true" is not defined
+            # No 'Condition cannot be inferred' error here since it's already
+            # emitted on the first condition, 'bool_var', above.
+        @overload
+        def f4(x: C) -> C: ...
+    else:
+        @overload
+        def f4(x: D) -> D: ...
+    @overload
+    def f4(x: E) -> E: ...
+    def f4(x): ...
+    reveal_type(f4(E()))  # N: Revealed type is "__main__.E"
+    reveal_type(f4(B()))  # E: No overload variant of "f4" matches argument type "B" \
+        # N: Possible overload variants: \
+        # N:     def f4(x: A) -> A \
+        # N:     def f4(x: E) -> E \
+        # N: Revealed type is "Any"
+
+[case testOverloadIfSkipUnknownExecution]
+# flags: --always-true True
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# If blocks should be skipped if execution can't be certain
+# Overload name must match outer name
+# -----
+
+@overload  # E: Single overload definition, multiple required
+def f1(x: A) -> A: ...
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f1(x: B) -> B: ...
+def f1(x): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f2(x: A) -> A: ...
+@overload
+def f2(x: B) -> B: ...
+@overload
+def f2(x: C) -> C: ...
+def f2(x): ...
+reveal_type(f2(A()))  # E: No overload variant of "f2" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f2(x: B) -> B \
+    # N:     def f2(x: C) -> C \
+    # N: Revealed type is "Any"
+
+if True:
+    @overload  # E: Single overload definition, multiple required
+    def f3(x: A) -> A: ...
+    if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                    # E: Name "maybe_true" is not defined
+        @overload
+        def f3(x: B) -> B: ...
+    def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+
+if True:
+    if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                    # E: Name "maybe_true" is not defined
+        @overload
+        def f4(x: A) -> A: ...
+    @overload
+    def f4(x: B) -> B: ...
+    @overload
+    def f4(x: C) -> C: ...
+    def f4(x): ...
+reveal_type(f4(A()))  # E: No overload variant of "f4" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f4(x: B) -> B \
+    # N:     def f4(x: C) -> C \
+    # N: Revealed type is "Any"
+
+[case testOverloadIfDontSkipUnrelatedOverload]
+# flags: --always-true True
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# Don't skip if block if overload name doesn't match outer name
+# -----
+
+@overload  # E: Single overload definition, multiple required
+def f1(x: A) -> A: ...
+if maybe_true:  # E: Name "maybe_true" is not defined
+    @overload  # E: Single overload definition, multiple required
+    def g1(x: B) -> B: ...
+def f1(x): ...  # E: Name "f1" already defined on line 13
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+
+if maybe_true:  # E: Name "maybe_true" is not defined
+    @overload  # E: Single overload definition, multiple required
+    def g2(x: A) -> A: ...
+@overload
+def f2(x: B) -> B: ...
+@overload
+def f2(x: C) -> C: ...
+def f2(x): ...
+reveal_type(f2(A()))  # E: No overload variant of "f2" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f2(x: B) -> B \
+    # N:     def f2(x: C) -> C \
+    # N: Revealed type is "Any"
+
+if True:
+    @overload  # E: Single overload definition, multiple required
+    def f3(x: A) -> A: ...
+    if maybe_true:  # E: Name "maybe_true" is not defined
+        @overload  # E: Single overload definition, multiple required
+        def g3(x: B) -> B: ...
+    def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+
+if True:
+    if maybe_true:  # E: Name "maybe_true" is not defined
+        @overload  # E: Single overload definition, multiple required
+        def g4(x: A) -> A: ...
+    @overload
+    def f4(x: B) -> B: ...
+    @overload
+    def f4(x: C) -> C: ...
+    def f4(x): ...
+reveal_type(f4(A()))  # E: No overload variant of "f4" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f4(x: B) -> B \
+    # N:     def f4(x: C) -> C \
+    # N: Revealed type is "Any"
+
+[case testOverloadIfNotMergingDifferentNames]
+# flags: --always-true True
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# Don't merge overloads if IfStmts contains overload with different name
+# -----
+
+@overload  # E: An overloaded function outside a stub file must have an implementation
+def f1(x: A) -> A: ...
+@overload
+def f1(x: B) -> B: ...
+if True:
+    @overload  # E: Single overload definition, multiple required
+    def g1(x: C) -> C: ...
+def f1(x): ...  # E: Name "f1" already defined on line 13
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(C()))  # E: No overload variant of "f1" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f1(x: A) -> A \
+    # N:     def f1(x: B) -> B \
+    # N: Revealed type is "Any"
+
+if True:
+    @overload  # E: Single overload definition, multiple required
+    def g2(x: A) -> A: ...
+@overload
+def f2(x: B) -> B: ...
+@overload
+def f2(x: C) -> C: ...
+def f2(x): ...
+reveal_type(f2(A()))  # E: No overload variant of "f2" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f2(x: B) -> B \
+    # N:     def f2(x: C) -> C \
+    # N: Revealed type is "Any"
+reveal_type(f2(B()))  # N: Revealed type is "__main__.B"
+
+if True:
+    if True:
+        @overload  # E: Single overload definition, multiple required
+        def g3(x: A) -> A: ...
+    @overload
+    def f3(x: B) -> B: ...
+    @overload
+    def f3(x: C) -> C: ...
+    def f3(x): ...
+reveal_type(f3(A()))  # E: No overload variant of "f3" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f3(x: B) -> B \
+    # N:     def f3(x: C) -> C \
+    # N: Revealed type is "Any"
+reveal_type(f3(B()))  # N: Revealed type is "__main__.B"
+
+[case testOverloadIfSplitFunctionDef]
+# flags: --always-true True --always-false False
+from typing import overload
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+# -----
+# Test split FuncDefs
+# -----
+
+@overload
+def f1(x: A) -> A: ...
+@overload
+def f1(x: B) -> B: ...
+if True:
+    def f1(x): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+
+@overload
+def f2(x: A) -> A: ...
+@overload
+def f2(x: B) -> B: ...
+if False:
+    def f2(x): ...
+else:
+    def f2(x): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+
+@overload  # E: An overloaded function outside a stub file must have an implementation
+def f3(x: A) -> A: ...
+@overload
+def f3(x: B) -> B: ...
+if True:
+    def f3(x): ...   # E: Name "f3" already defined on line 31
+else:
+    pass  # some other node
+    def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+
+[case testOverloadIfMixed]
+# flags: --always-true True --always-false False
+from typing import overload, TYPE_CHECKING
+
+class A: ...
+class B: ...
+class C: ...
+class D: ...
+
+if maybe_var:  # E: Name "maybe_var" is not defined
+    pass
+if True:
+    @overload
+    def f1(x: A) -> A: ...
+@overload
+def f1(x: B) -> B: ...
+def f1(x): ...
+reveal_type(f1(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f1(B()))  # N: Revealed type is "__main__.B"
+
+if True:
+    @overload
+    def f2(x: A) -> A: ...
+    @overload
+    def f2(x: B) -> B: ...
+def f2(x): ...
+reveal_type(f2(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f2(B()))  # N: Revealed type is "__main__.B"
+
+if True:
+    @overload
+    def f3(x: A) -> A: ...
+    @overload
+    def f3(x: B) -> B: ...
+    def f3(x): ...
+reveal_type(f3(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f3(B()))  # N: Revealed type is "__main__.B"
+
+# Don't crash with AssignmentStmt if elif
+@overload  # E: Single overload definition, multiple required
+def f4(x: A) -> A: ...
+if False:
+    @overload
+    def f4(x: B) -> B: ...
+elif True:
+    var = 1
+def f4(x): ...  # E: Name "f4" already defined on line 39
+
+if TYPE_CHECKING:
+    @overload
+    def f5(x: A) -> A: ...
+    @overload
+    def f5(x: B) -> B: ...
+def f5(x): ...
+reveal_type(f5(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f5(B()))  # N: Revealed type is "__main__.B"
+
+# Test from check-functions - testUnconditionalRedefinitionOfConditionalFunction
+# Don't merge If blocks if they appear before any overloads
+# and don't contain any overloads themselves.
+if maybe_true:  # E: Name "maybe_true" is not defined
+    def f6(x): ...
+def f6(x): ...  # E: Name "f6" already defined on line 61
+
+if maybe_true:  # E: Name "maybe_true" is not defined
+    pass  # Some other node
+    def f7(x): ...
+def f7(x): ...  # E: Name "f7" already defined on line 66
+
+@overload
+def f8(x: A) -> A: ...
+@overload
+def f8(x: B) -> B: ...
+if False:
+    def f8(x: C) -> C: ...
+def f8(x): ...
+reveal_type(f8(A()))  # N: Revealed type is "__main__.A"
+reveal_type(f8(C()))  # E: No overload variant of "f8" matches argument type "C" \
+    # N: Possible overload variants: \
+    # N:     def f8(x: A) -> A \
+    # N:     def f8(x: B) -> B \
+    # N: Revealed type is "Any"
+
+if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                # E: Name "maybe_true" is not defined
+    @overload
+    def f9(x: A) -> A: ...
+if another_maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                        # E: Name "another_maybe_true" is not defined
+    @overload
+    def f9(x: B) -> B: ...
+@overload
+def f9(x: C) -> C: ...
+@overload
+def f9(x: D) -> D: ...
+def f9(x): ...
+reveal_type(f9(A()))  # E: No overload variant of "f9" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f9(x: C) -> C \
+    # N:     def f9(x: D) -> D \
+    # N: Revealed type is "Any"
+reveal_type(f9(C()))  # N: Revealed type is "__main__.C"
+
+if True:
+    if maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                    # E: Name "maybe_true" is not defined
+        @overload
+        def f10(x: A) -> A: ...
+    if another_maybe_true:  # E: Condition cannot be inferred, unable to merge overloads \
+                            # E: Name "another_maybe_true" is not defined
+        @overload
+        def f10(x: B) -> B: ...
+    @overload
+    def f10(x: C) -> C: ...
+    @overload
+    def f10(x: D) -> D: ...
+    def f10(x): ...
+reveal_type(f10(A()))  # E: No overload variant of "f10" matches argument type "A" \
+    # N: Possible overload variants: \
+    # N:     def f10(x: C) -> C \
+    # N:     def f10(x: D) -> D \
+    # N: Revealed type is "Any"
+reveal_type(f10(C()))  # N: Revealed type is "__main__.C"
+
+if some_var:  # E: Name "some_var" is not defined
+    pass
+@overload
+def f11(x: A) -> A: ...
+@overload
+def f11(x: B) -> B: ...
+def f11(x): ...
+reveal_type(f11(A()))  # N: Revealed type is "__main__.A"
+
+if True:
+    if some_var:  # E: Name "some_var" is not defined
+        pass
+    @overload
+    def f12(x: A) -> A: ...
+    @overload
+    def f12(x: B) -> B: ...
+    def f12(x): ...
+reveal_type(f12(A()))  # N: Revealed type is "__main__.A"
+[typing fixtures/typing-medium.pyi]