Repository: python/mypy

Issue:
Check for redefinition of function uses contravariance in return type instead of covariance
Two classes:

```
class C:
    pass

class D(C):
    pass
```

Two functions:

```
def f() -> C:
    pass

def g() -> D:
    pass
```

Now use conditional redefinition:

```
if True:
    v3 = f
else:
    def v3() -> D:
        pass
```

This should be correct, but we get an error on `def v3() -> D`:

```
error: Incompatible redefinition (original type Callable[[], C], redefinition with type Callable[[], D])
```

OTOH if we reverse roles we don't get an error:

```
if True:
    v3 = g
else:
    def v4() -> C:  # No error here?!
        pass
```

The code for regular assignment gets it right:

```
v1 = f
v1 = g  # No error

v2 = g
v2 = f  # Error here
```

The error on the last line is:

```
error: Incompatible types in assignment (expression has type Callable[[], C], variable has type Callable[[], D])
```



Discussion:
@JukkaL If you agree, this is probably a simple reversal of a subtype check? Or am I missing something? (Co/contravariance always gives me a headache.)

Yeah, conditional redefinition with a narrower return type should be okay. The subtype check seems to have been reversed.

More generally, the order of definition vs. redefinition shouldn't matter. Mypy should pick the more general type as the type of the function. However, this is a separate issue.


Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -598,10 +598,10 @@ def visit_func_def(self, defn: FuncDef) -> Type:
                         self.fail(messages.INCOMPATIBLE_REDEFINITION, defn)
                 else:
                     # TODO: Update conditional type binder.
-                    self.check_subtype(orig_type, new_type, defn,
+                    self.check_subtype(new_type, orig_type, defn,
                                        messages.INCOMPATIBLE_REDEFINITION,
-                                       'original type',
-                                       'redefinition with type')
+                                       'redefinition with type',
+                                       'original type')
 
     def check_func_item(self, defn: FuncItem,
                         type_override: CallableType = None,


Test patch:
diff --git a/mypy/test/data/check-functions.test b/mypy/test/data/check-functions.test
--- a/mypy/test/data/check-functions.test
+++ b/mypy/test/data/check-functions.test
@@ -1146,7 +1146,23 @@ main: note: In function "g":
 def g(): pass
 f = g
 if g():
-    def f(x): pass  # E: Incompatible redefinition (original type Callable[[], Any], redefinition with type Callable[[Any], Any])
+    def f(x): pass  # E: Incompatible redefinition (redefinition with type Callable[[Any], Any], original type Callable[[], Any])
+
+[case testRedefineFunctionDefinedAsVariableWithVariance1]
+class B: pass
+class C(B): pass
+def g(x: C) -> B: pass
+f = g
+if g(C()):
+    def f(x: C) -> C: pass
+
+[case testRedefineFunctionDefinedAsVariableWithVariance2]
+class B: pass
+class C(B): pass
+def g(x: C) -> B: pass
+f = g
+if g(C()):
+    def f(x: B) -> B: pass
 
 [case testRedefineFunctionDefinedAsVariableInitializedToEmptyList]
 f = [] # E: Need type annotation for variable