From 6a9ed14569fc95c3e47635030744eb3f16e44dbb Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sat, 31 Dec 2016 21:19:59 -0800 Subject: [PATCH 1/5] Make TypeVars with only one constraint illegal At runtime, doing something like `T = TypeVar('T', str)` results in the following exception: TypeError: A single constraint is not allowed However, mypy itself seems to ignore this particular error, which feels incongruous. This commit slightly tweaks the semanal phase to emit an error message in this case to mirror the existing runtime behavior implemented in the typing module. --- mypy/semanal.py | 8 ++++++-- test-data/unit/semanal-errors.test | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8771fb768d80..675ea9681c19 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1544,7 +1544,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> None: res = self.process_typevar_parameters(call.args[1 + n_values:], call.arg_names[1 + n_values:], call.arg_kinds[1 + n_values:], - bool(values), + n_values, s) if res is None: return @@ -1591,8 +1591,9 @@ def get_typevar_declaration(self, s: AssignmentStmt) -> Optional[CallExpr]: def process_typevar_parameters(self, args: List[Expression], names: List[Optional[str]], kinds: List[int], - has_values: bool, + num_values: int, context: Context) -> Optional[Tuple[int, Type]]: + has_values = (num_values > 0) covariant = False contravariant = False upper_bound = self.object_type() # type: Type @@ -1642,6 +1643,9 @@ def process_typevar_parameters(self, args: List[Expression], if covariant and contravariant: self.fail("TypeVar cannot be both covariant and contravariant", context) return None + elif num_values == 1: + self.fail("TypeVar cannot have only a single constraint", context) + return None elif covariant: variance = COVARIANT elif contravariant: diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 06fd3d0dd226..f28476f1d110 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -969,9 +969,10 @@ b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument c = TypeVar(1) # E: TypeVar() expects a string literal as first argument d = TypeVar('D') # E: String argument 1 'D' to TypeVar(...) does not match variable name 'd' e = TypeVar('e', int, str, x=1) # E: Unexpected argument to TypeVar(): x -f = TypeVar('f', (int, str)) # E: Type expected -g = TypeVar('g', x=(int, str)) # E: Unexpected argument to TypeVar(): x -h = TypeVar('h', bound=1) # E: TypeVar 'bound' must be a type +f = TypeVar('f', (int, str), int) # E: Type expected +g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint +h = TypeVar('h', x=(int, str)) # E: Unexpected argument to TypeVar(): x +i = TypeVar('i', bound=1) # E: TypeVar 'bound' must be a type [out] [case testMoreInvalidTypevarArguments] From 338c559a26f669055f07921d2b9738b4e7c1471f Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sat, 31 Dec 2016 21:43:28 -0800 Subject: [PATCH 2/5] Fix (previously missed) broken unit tests This commit fixes some broken unit tests that appeared to have been using TypeVar incorrectly. This commit does not cause the entire test suite to pass due to existing errors within typeshed that need to be fixed first. --- test-data/unit/check-bound.test | 2 +- test-data/unit/check-classes.test | 2 +- test-data/unit/check-typevar-values.test | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-bound.test b/test-data/unit/check-bound.test index ee935aed8bdd..95f7bddc5267 100644 --- a/test-data/unit/check-bound.test +++ b/test-data/unit/check-bound.test @@ -142,7 +142,7 @@ class A(A0): class B(A): def baz(self) -> None: pass -T = TypeVar('T', A) +T = TypeVar('T', bound=A) def f(x: T) -> T: x.foo() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index bdfa2de9b340..e53cf1711e14 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1458,7 +1458,7 @@ main:6: error: Signatures of "__radd__" of "B" and "__add__" of "X" are unsafely [case testUnsafeOverlappingWithLineNo] from typing import TypeVar -T = TypeVar('T', Real) +T = TypeVar('T', bound=Real) class Real: def __add__(self, other): ... class Fraction(Real): diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 69feab2059e4..34f5501f0a23 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -493,7 +493,7 @@ def outer(x: T) -> T: [case testClassMemberTypeVarInFunctionBody] from typing import TypeVar class C: - T = TypeVar('T', int) + T = TypeVar('T', bound=int) def f(self, x: T) -> T: A = C.T return x From 2f620ad2a21192ae5f95e4761608f9d4937a3832 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sat, 31 Dec 2016 22:07:16 -0800 Subject: [PATCH 3/5] Attempt 2 at fixing unit tests --- test-data/unit/check-bound.test | 2 +- test-data/unit/check-classes.test | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-bound.test b/test-data/unit/check-bound.test index 95f7bddc5267..bfe04e0cfb67 100644 --- a/test-data/unit/check-bound.test +++ b/test-data/unit/check-bound.test @@ -147,7 +147,7 @@ T = TypeVar('T', bound=A) def f(x: T) -> T: x.foo() x.bar() - x.baz() # E: "A" has no attribute "baz" + x.baz() # E: "T" has no attribute "baz" x.a x.b return x diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e53cf1711e14..7db2c2b88d66 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1458,13 +1458,12 @@ main:6: error: Signatures of "__radd__" of "B" and "__add__" of "X" are unsafely [case testUnsafeOverlappingWithLineNo] from typing import TypeVar -T = TypeVar('T', bound=Real) class Real: def __add__(self, other): ... class Fraction(Real): - def __radd__(self, other: T) -> T: ... + def __radd__(self, other: Real) -> Real: ... [out] -main:6: error: Signatures of "__radd__" of "Fraction" and "__add__" of "Real" are unsafely overlapping +main:5: error: Signatures of "__radd__" of "Fraction" and "__add__" of "Real" are unsafely overlapping [case testOverlappingNormalAndInplaceOperatorMethod] import typing From 55bbd5304ef26f071e49bc5f0a1e0599669e52eb Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 29 Mar 2017 15:01:09 -0700 Subject: [PATCH 4/5] Whitespace change to trigger tests. --- test-data/unit/semanal-errors.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index f28476f1d110..59bf9fe4efab 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -964,10 +964,10 @@ class A(metaclass=f): pass # E: Invalid metaclass 'f' [case testInvalidTypevarArguments] from typing import TypeVar -a = TypeVar() # E: Too few arguments for TypeVar() -b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument -c = TypeVar(1) # E: TypeVar() expects a string literal as first argument -d = TypeVar('D') # E: String argument 1 'D' to TypeVar(...) does not match variable name 'd' +a = TypeVar() # E: Too few arguments for TypeVar() +b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument +c = TypeVar(1) # E: TypeVar() expects a string literal as first argument +d = TypeVar('D') # E: String argument 1 'D' to TypeVar(...) does not match variable name 'd' e = TypeVar('e', int, str, x=1) # E: Unexpected argument to TypeVar(): x f = TypeVar('f', (int, str), int) # E: Type expected g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint From 549a07f42d88a7c9d1b51cb49acb2a2cdfa84ae6 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 29 Mar 2017 16:22:27 -0700 Subject: [PATCH 5/5] More whitespace changes to trigger tests But with synced typeshed. --- test-data/unit/semanal-errors.test | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 59bf9fe4efab..f7de00d1a40b 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -964,15 +964,15 @@ class A(metaclass=f): pass # E: Invalid metaclass 'f' [case testInvalidTypevarArguments] from typing import TypeVar -a = TypeVar() # E: Too few arguments for TypeVar() -b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument -c = TypeVar(1) # E: TypeVar() expects a string literal as first argument -d = TypeVar('D') # E: String argument 1 'D' to TypeVar(...) does not match variable name 'd' -e = TypeVar('e', int, str, x=1) # E: Unexpected argument to TypeVar(): x +a = TypeVar() # E: Too few arguments for TypeVar() +b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument +c = TypeVar(1) # E: TypeVar() expects a string literal as first argument +d = TypeVar('D') # E: String argument 1 'D' to TypeVar(...) does not match variable name 'd' +e = TypeVar('e', int, str, x=1) # E: Unexpected argument to TypeVar(): x f = TypeVar('f', (int, str), int) # E: Type expected -g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint -h = TypeVar('h', x=(int, str)) # E: Unexpected argument to TypeVar(): x -i = TypeVar('i', bound=1) # E: TypeVar 'bound' must be a type +g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint +h = TypeVar('h', x=(int, str)) # E: Unexpected argument to TypeVar(): x +i = TypeVar('i', bound=1) # E: TypeVar 'bound' must be a type [out] [case testMoreInvalidTypevarArguments]