Repository: python/mypy

Issue:
Cannot determine type error with TypeVar with value constraint
Assuming the following code snippet:

```python
from typing import Callable, TypeVar, Generic

T = TypeVar('T', str, int)
Printer = Callable[[T], None]

class Klass(Generic[T]):
    def __init__(self, msg: str, p: Printer[T]) -> None:
        self.msg = msg
        self.p = p

    def echo(self) -> None:
        print(self.msg)
```
`mypy` returns error for `print(self.msg)`: `error: Cannot determine type of 'msg'`

`reveal_type` fails to determine type of `self.msg`:
`reveal_type(self.msg)` results in 
```
error: Cannot determine type of 'msg'
Revealed type is 'Any'
```

If we changed type variable to unconstrained, `T = TypeVar('T')`, everything works as expected.


Discussion:
I can repro this but don't understand it yet. The Printer alias and p
argument are not even needed.

--Guido (mobile)

On Jan 16, 2017 8:21 AM, "Mohab Usama" <notifications@github.com> wrote:

Assuming the following code snippet:

from typing import Callable, TypeVar, Generic
T = TypeVar('T', str, int)
Printer = Callable[[T], None]
class Klass(Generic[T]):
    def __init__(self, msg: str, p: Printer[T]) -> None:
        self.msg = msg
        self.p = p

    def echo(self) -> None:
        print(self.msg)

mypy returns error for print(self.msg): error: Cannot determine type of
'msg'

reveal_type fails to determine type of self.msg:
reveal_type(self.msg) results in

error: Cannot determine type of 'msg'
Revealed type is 'Any'

If we changed type variable to unconstrained, T = TypeVar('T'), everything
works as expected.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<https://github.com/python/mypy/issues/2697>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/ACwrMt_sTrJ6BaRW6jeW-q4M6eOVef5Pks5rS5kKgaJpZM4LkvUr>
.

@gvanrossum True indeed. Here is a simpler version:

```python
from typing import TypeVar, Generic

T = TypeVar('T', str, int)

class Klass(Generic[T]):
    def __init__(self, msg: str) -> None:
        self.msg = msg

    def echo(self) -> None:
        print(self.msg)
```
Even simpler:
```python
from typing import TypeVar, Generic
T = TypeVar('T', int, str)
class C(Generic[T]):
    def __init__(self) -> None:
        self.msg = ''
        reveal_type(self.msg)
```
gives
```
__tmp__.py:6: error: Cannot determine type of 'msg'
__tmp__.py:6: error: Revealed type is 'Any'
```
Oh wait, @JukkaL mentioned this on [gitter](https://gitter.im/python/mypy?at=587cf052074f7be763da0db3): "my reading is that defining an attribute in a generic method that uses a constrained type variable is broken."
Raising priority since this appeared again and it looks "fundamental".

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -124,6 +124,9 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
     # Should strict Optional-related errors be suppressed in this file?
     suppress_none_errors = False  # TODO: Get it from options instead
     options = None  # type: Options
+    # Used for collecting inferred attribute types so that they can be checked
+    # for consistency.
+    inferred_attribute_types = None  # type: Optional[Dict[Var, Type]]
 
     # The set of all dependencies (suppressed or not) that this module accesses, either
     # directly or indirectly.
@@ -160,6 +163,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
         self.current_node_deferred = False
         self.is_stub = tree.is_stub
         self.is_typeshed_stub = errors.is_typeshed_file(path)
+        self.inferred_attribute_types = None
         if options.strict_optional_whitelist is None:
             self.suppress_none_errors = not options.show_none_errors
         else:
@@ -575,13 +579,21 @@ def check_func_item(self, defn: FuncItem,
                 if type_override:
                     typ = type_override
                 if isinstance(typ, CallableType):
-                    self.check_func_def(defn, typ, name)
+                    with self.enter_attribute_inference_context():
+                        self.check_func_def(defn, typ, name)
                 else:
                     raise RuntimeError('Not supported')
 
         self.dynamic_funcs.pop()
         self.current_node_deferred = False
 
+    @contextmanager
+    def enter_attribute_inference_context(self) -> Iterator[None]:
+        old_types = self.inferred_attribute_types
+        self.inferred_attribute_types = {}
+        yield None
+        self.inferred_attribute_types = old_types
+
     def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None:
         """Type check a function definition."""
         # Expand type variables with value restrictions to ordinary types.
@@ -1691,6 +1703,12 @@ def infer_variable_type(self, name: Var, lvalue: Lvalue,
             if not self.infer_partial_type(name, lvalue, init_type):
                 self.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
                 self.set_inference_error_fallback_type(name, lvalue, init_type, context)
+        elif (isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None
+              and lvalue.def_var in self.inferred_attribute_types
+              and not is_same_type(self.inferred_attribute_types[lvalue.def_var], init_type)):
+            # Multiple, inconsistent types inferred for an attribute.
+            self.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
+            name.type = AnyType()
         else:
             # Infer type of the target.
 
@@ -1727,6 +1745,9 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None:
         if var and not self.current_node_deferred:
             var.type = type
             var.is_inferred = True
+            if isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None:
+                # Store inferred attribute type so that we can check consistency afterwards.
+                self.inferred_attribute_types[lvalue.def_var] = type
             self.store_type(lvalue, type)
 
     def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type,
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -1247,7 +1247,8 @@ class MemberExpr(RefExpr):
 
     expr = None  # type: Expression
     name = None  # type: str
-    # The variable node related to a definition.
+    # The variable node related to a definition through 'self.x = <initializer>'.
+    # The nodes of other kinds of member expressions are resolved during type checking.
     def_var = None  # type: Var
 
     def __init__(self, expr: Expression, name: str) -> None:
diff --git a/mypy/treetransform.py b/mypy/treetransform.py
--- a/mypy/treetransform.py
+++ b/mypy/treetransform.py
@@ -340,7 +340,9 @@ def visit_member_expr(self, node: MemberExpr) -> MemberExpr:
         member = MemberExpr(self.expr(node.expr),
                             node.name)
         if node.def_var:
-            member.def_var = self.visit_var(node.def_var)
+            # This refers to an attribute and we don't transform attributes by default,
+            # just normal variables.
+            member.def_var = node.def_var
         self.copy_ref(member, node)
         return member
 


Test patch:
diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test
--- a/test-data/unit/check-typevar-values.test
+++ b/test-data/unit/check-typevar-values.test
@@ -312,6 +312,52 @@ cs = C() # type: C[str]
 cs.x = ''
 cs.x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
 
+[case testAttributeInGenericTypeWithTypevarValues3]
+from typing import TypeVar, Generic
+X = TypeVar('X', int, str)
+class C(Generic[X]):
+    def f(self, x: X) -> None:
+        self.x = x  # type: X
+ci: C[int]
+cs: C[str]
+reveal_type(ci.x) # E: Revealed type is 'builtins.int*'
+reveal_type(cs.x) # E: Revealed type is 'builtins.str*'
+
+[case testAttributeInGenericTypeWithTypevarValuesUsingInference1]
+from typing import TypeVar, Generic
+X = TypeVar('X', int, str)
+class C(Generic[X]):
+    def f(self, x: X) -> None:
+        self.x = x # E: Need type annotation for variable
+ci: C[int]
+cs: C[str]
+reveal_type(ci.x) # E: Revealed type is 'Any'
+reveal_type(cs.x) # E: Revealed type is 'Any'
+
+[case testAttributeInGenericTypeWithTypevarValuesUsingInference2]
+from typing import TypeVar, Generic
+X = TypeVar('X', int, str)
+class C(Generic[X]):
+    def f(self, x: X) -> None:
+        self.x = 1
+        reveal_type(self.x) # E: Revealed type is 'builtins.int'
+ci: C[int]
+cs: C[str]
+reveal_type(ci.x) # E: Revealed type is 'builtins.int'
+reveal_type(cs.x) # E: Revealed type is 'builtins.int'
+
+[case testAttributeInGenericTypeWithTypevarValuesUsingInference3]
+from typing import TypeVar, Generic
+X = TypeVar('X', int, str)
+class C(Generic[X]):
+    x: X
+    def f(self) -> None:
+        self.y = self.x # E: Need type annotation for variable
+ci: C[int]
+cs: C[str]
+reveal_type(ci.y) # E: Revealed type is 'Any'
+reveal_type(cs.y) # E: Revealed type is 'Any'
+
 [case testInferredAttributeInGenericClassBodyWithTypevarValues]
 from typing import TypeVar, Generic
 X = TypeVar('X', int, str)
@@ -403,6 +449,19 @@ c(g(1))
 main:6: error: Argument 1 to "c" has incompatible type "str"; expected "ss"
 main:7: error: Argument 1 to "c" has incompatible type "int"; expected "ss"
 
+[case testDefineAttributeInGenericMethodUsingTypeVarWithValues]
+from typing import TypeVar
+T = TypeVar('T', int, str)
+class A:
+    def f(self, x: T) -> None:
+        self.x = x # E: Need type annotation for variable
+        self.y = [x] # E: Need type annotation for variable
+        self.z = 1
+reveal_type(A().x)  # E: Revealed type is 'Any'
+reveal_type(A().y)  # E: Revealed type is 'Any'
+reveal_type(A().z)  # E: Revealed type is 'builtins.int'
+[builtins fixtures/list.pyi]
+
 
 -- Special cases
 -- -------------