Repository: python/mypy

Issue:
Variables initialized to None and then to something else don't work with --strict-optional
This works without `--strict-optional`:

``` python
def foo() -> str:
    a = None
    a = ''
    return a
```

But with `--strict-optional` we get a complaint like this:

```
error: Incompatible return value type (got "Optional[str]", expected "str")
```

**[UPDATE: the rest is incorrect, please ignore]**
If I annotate the initial assignment again it works without `--strict-optional`:

``` python
def foo() -> str:
    a = None  # type: None
    a = ''
    return a
```

But with `--strict-optional` I get this on the first assignment:

```
error: Incompatible types in assignment (expression has type None, variable has type "str")
```

I think both of these ought to work under `--strict-optional`.

I don't know if this requires changes to the binder or elsewhere or both.



Discussion:
The second example should definitely not work -- you're explicitly saying the type of `a` is `None`, and the only value that's a member of `None` is `None`.  Did you mean:

``` py
from typing import Optional
def foo() -> str:
    a = None  # type: Optional[str]
    a = ''
    return a
```

This works fine with --strict-optional.

I agree that the first case should work, though.  I think that's related to partial types -- we probably don't add "`a` is a str" to the binder when turning the partial type into an optional type.  Might not be too difficult of a fix.

Oh, forget about the second example. But the first should work.


Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -929,6 +929,7 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool =
                         # This doesn't actually provide any additional information -- multiple
                         # None initializers preserve the partial None type.
                         return
+
                     if is_valid_inferred_type(rvalue_type):
                         var = lvalue_type.var
                         partial_types = self.find_partial_types(var)
@@ -942,11 +943,12 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool =
                             else:
                                 var.type = None
                             del partial_types[var]
-                    # Try to infer a partial type. No need to check the return value, as
-                    # an error will be reported elsewhere.
-                    self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type)
-                    return
-                if (is_literal_none(rvalue) and
+                            lvalue_type = var.type
+                    else:
+                        # Try to infer a partial type. No need to check the return value, as
+                        # an error will be reported elsewhere.
+                        self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type)
+                elif (is_literal_none(rvalue) and
                         isinstance(lvalue, NameExpr) and
                         isinstance(lvalue.node, Var) and
                         lvalue.node.is_initialized_in_class):


Test patch:
diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test
--- a/test-data/unit/check-optional.test
+++ b/test-data/unit/check-optional.test
@@ -107,8 +107,20 @@ main: note: In function "f":
 
 [case testInferOptionalType]
 x = None
-x = 1
+if bool():
+  # scope limit assignment
+  x = 1
+  # in scope of the assignment, x is an int
+  reveal_type(x)  # E: Revealed type is 'builtins.int'
+# out of scope of the assignment, it's an Optional[int]
 reveal_type(x)  # E: Revealed type is 'Union[builtins.int, builtins.None]'
+[builtins fixtures/bool.py]
+
+[case testInferOptionalTypeLocallyBound]
+x = None
+x = 1
+reveal_type(x)  # E: Revealed type is 'builtins.int'
+
 
 [case testInferOptionalTypeFromOptional]
 from typing import Optional