From d54708dbd782e85572e70157f7368f4532c847ca Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 01:46:23 -0700 Subject: [PATCH 1/7] Make it an error to use a class-attribute type var outside a type Previously, e9d28a01 fixed a crash when you tried to access a class-attribute type variable. The test in that commit involved assigning a variable the value of the typevar. It resulted in no crash, but rather treating the variable as being an instance of the type the typevar bound to, later, which is incorrect. Instead, this PR treats such an assignment as an error, and gives you the same message as when you try to alias a typevar directly. Also test a *correct* alias with the typevar access method in question -- it works. --- mypy/checkmember.py | 4 +++- mypy/typeanal.py | 3 +++ test-data/unit/check-typevar-values.test | 11 ++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ede8b9189698..056148c96c92 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -406,7 +406,9 @@ def analyze_class_attribute_access(itype: Instance, return AnyType() if isinstance(node.node, TypeVarExpr): - return TypeVarType(node.tvar_def, node.tvar_def.line, node.tvar_def.column) + msg.fail('Type variable "{}" is invalid as target for type alias'.format( + node.node.fullname() or node.node.name()), context) + return AnyType() if isinstance(node.node, TypeInfo): return type_object_type(node.node, builtin_type) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index dc1e22a32969..bbafdf362a15 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -43,6 +43,9 @@ def analyze_type_alias(node: Expression, # that we don't support straight string literals as type aliases # (only string literals within index expressions). if isinstance(node, RefExpr): + # Note that this misses the case where someone tried to use a + # class-referenced type variable as a type alias. It's easier to catch + # that one in checkmemeber.py if node.kind == UNBOUND_TVAR or node.kind == BOUND_TVAR: fail_func('Type variable "{}" is invalid as target for type alias'.format( node.fullname), node) diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index f41710ee155b..bd008d5b44b6 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -496,12 +496,17 @@ def outer(x: T) -> T: [out] [case testClassMemberTypeVarInFunctionBody] -from typing import TypeVar +from typing import TypeVar, List class C: T = TypeVar('T', bound=int) def f(self, x: T) -> T: - A = C.T - return x + L = List[C.T] # a valid type alias + l: L = [] + l.append(x) + A = C.T # E: Type variable "T" is invalid as target for type alias + return l[0] + +[builtins fixtures/list.pyi] [case testParameterLessGenericAsRestriction] from typing import Sequence, Iterable, TypeVar From fe6eee173d611bb574ef5e2f1200919da0f0e125 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 02:09:52 -0700 Subject: [PATCH 2/7] Fix the error message to handle more cases --- mypy/checkmember.py | 4 ++-- test-data/unit/check-generics.test | 1 + test-data/unit/check-typevar-values.test | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 056148c96c92..a70292f3d736 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -406,8 +406,8 @@ def analyze_class_attribute_access(itype: Instance, return AnyType() if isinstance(node.node, TypeVarExpr): - msg.fail('Type variable "{}" is invalid as target for type alias'.format( - node.node.fullname() or node.node.name()), context) + msg.fail('Type variable "{}" is invalid except as a parameter to another type'.format( + node.node.fullname() or node.node.name()), context) return AnyType() if isinstance(node.node, TypeInfo): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 7d035d0ed992..a0f4dbc5ad9a 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -14,6 +14,7 @@ class A(Generic[T]): class B: pass class C: pass + [out] main:4: error: Incompatible types in assignment (expression has type "B", variable has type "C") diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index bd008d5b44b6..86350344e2be 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -503,7 +503,8 @@ class C: L = List[C.T] # a valid type alias l: L = [] l.append(x) - A = C.T # E: Type variable "T" is invalid as target for type alias + C.T # E: Type variable "T" is invalid except as a parameter to another type + A = C.T # E: Type variable "T" is invalid except as a parameter to another type return l[0] [builtins fixtures/list.pyi] From 7b8e409af5d6e8879bc814544bc7c253dae2f8bc Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 02:19:52 -0700 Subject: [PATCH 3/7] oops accidentally changed a file --- test-data/unit/check-generics.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index a0f4dbc5ad9a..7d035d0ed992 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -14,7 +14,6 @@ class A(Generic[T]): class B: pass class C: pass - [out] main:4: error: Incompatible types in assignment (expression has type "B", variable has type "C") From 8bfd1a749bff260c0d7028955e9db5fffef9a25f Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 10:45:30 -0700 Subject: [PATCH 4/7] Typo fix --- mypy/typeanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index bbafdf362a15..d5132421f8d5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -45,7 +45,7 @@ def analyze_type_alias(node: Expression, if isinstance(node, RefExpr): # Note that this misses the case where someone tried to use a # class-referenced type variable as a type alias. It's easier to catch - # that one in checkmemeber.py + # that one in checkmember.py if node.kind == UNBOUND_TVAR or node.kind == BOUND_TVAR: fail_func('Type variable "{}" is invalid as target for type alias'.format( node.fullname), node) From 7d658c6e8e9398f9465a0de9200740d2afee8f20 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 11:14:47 -0700 Subject: [PATCH 5/7] more checking things in tests --- test-data/unit/check-typevar-values.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 86350344e2be..56ffbf3a5489 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -502,6 +502,8 @@ class C: def f(self, x: T) -> T: L = List[C.T] # a valid type alias l: L = [] + reveal_type(l) # E: Revealed type is 'builtins.list[T`-1]' + y: C.T = x l.append(x) C.T # E: Type variable "T" is invalid except as a parameter to another type A = C.T # E: Type variable "T" is invalid except as a parameter to another type From 54ae7aab0538deb5dbf54ce3d8486afe124e2bf8 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 11:28:38 -0700 Subject: [PATCH 6/7] Change error message --- mypy/checkmember.py | 4 ++-- test-data/unit/check-typevar-values.test | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a70292f3d736..83c4660cb1b1 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -406,8 +406,8 @@ def analyze_class_attribute_access(itype: Instance, return AnyType() if isinstance(node.node, TypeVarExpr): - msg.fail('Type variable "{}" is invalid except as a parameter to another type'.format( - node.node.fullname() or node.node.name()), context) + msg.fail('Type variable "{}.{}" is invalid in a runtime context'.format( + itype.type.name(), name), context) return AnyType() if isinstance(node.node, TypeInfo): diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 56ffbf3a5489..29c72add0624 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -505,8 +505,8 @@ class C: reveal_type(l) # E: Revealed type is 'builtins.list[T`-1]' y: C.T = x l.append(x) - C.T # E: Type variable "T" is invalid except as a parameter to another type - A = C.T # E: Type variable "T" is invalid except as a parameter to another type + C.T # E: Type variable "C.T" is invalid in a runtime context + A = C.T # E: Type variable "C.T" is invalid in a runtime context return l[0] [builtins fixtures/list.pyi] From 0aa5a7051ec43ce61a8569fee14f18655a5efef0 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sat, 1 Apr 2017 11:30:27 -0700 Subject: [PATCH 7/7] Change error message --- mypy/checkmember.py | 2 +- test-data/unit/check-typevar-values.test | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 83c4660cb1b1..6c934dccb022 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -406,7 +406,7 @@ def analyze_class_attribute_access(itype: Instance, return AnyType() if isinstance(node.node, TypeVarExpr): - msg.fail('Type variable "{}.{}" is invalid in a runtime context'.format( + msg.fail('Type variable "{}.{}" cannot be used as an expression'.format( itype.type.name(), name), context) return AnyType() diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 29c72add0624..36df2235a209 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -505,8 +505,8 @@ class C: reveal_type(l) # E: Revealed type is 'builtins.list[T`-1]' y: C.T = x l.append(x) - C.T # E: Type variable "C.T" is invalid in a runtime context - A = C.T # E: Type variable "C.T" is invalid in a runtime context + C.T # E: Type variable "C.T" cannot be used as an expression + A = C.T # E: Type variable "C.T" cannot be used as an expression return l[0] [builtins fixtures/list.pyi]