Skip to content

Commit

Permalink
Disallow TypeVar constraints parameterized by type variables
Browse files Browse the repository at this point in the history
  • Loading branch information
brianschubert committed Nov 25, 2024
1 parent 499adae commit 8410966
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 5 deletions.
17 changes: 13 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2369,8 +2369,11 @@ def analyze_unbound_tvar_impl(
assert isinstance(sym.node, TypeVarExpr)
return t.name, sym.node

def find_type_var_likes(self, t: Type) -> TypeVarLikeList:
def find_type_var_likes(
self, t: Type, *, include_bound_tvars: bool = False
) -> TypeVarLikeList:
visitor = FindTypeVarVisitor(self, self.tvar_scope)
visitor.include_bound_tvars = include_bound_tvars
t.accept(visitor)
return visitor.type_var_likes

Expand Down Expand Up @@ -5036,9 +5039,15 @@ def analyze_value_types(self, items: list[Expression]) -> list[Type]:
result: list[Type] = []
for node in items:
try:
analyzed = self.anal_type(
self.expr_to_unanalyzed_type(node), allow_placeholder=True
)
unanalyzed_type = self.expr_to_unanalyzed_type(node)
if self.find_type_var_likes(unanalyzed_type, include_bound_tvars=True):
self.fail(
"TypeVar constraint type cannot be parametrized by type variables", node
)
result.append(AnyType(TypeOfAny.from_error))
continue

analyzed = self.anal_type(unanalyzed_type, allow_placeholder=True)
if analyzed is None:
# Type variables are special: we need to place them in the symbol table
# soon, even if some value is not ready yet, see process_typevar_parameters()
Expand Down
3 changes: 2 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,7 @@ def __init__(self, api: SemanticAnalyzerCoreInterface, scope: TypeVarLikeScope)
self.has_self_type = False
self.seen_aliases: set[TypeAliasType] | None = None
self.include_callables = True
self.include_bound_tvars = False

def _seems_like_callable(self, type: UnboundType) -> bool:
if not type.args:
Expand All @@ -2583,7 +2584,7 @@ def visit_unbound_type(self, t: UnboundType) -> None:
if (
node
and isinstance(node.node, TypeVarLikeExpr)
and self.scope.get_binding(node) is None
and (self.scope.get_binding(node) is None or self.include_bound_tvars)
):
if (name, node.node) not in self.type_var_likes:
self.type_var_likes.append((name, node.node))
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,19 @@ S = TypeVar('S', covariant=True, contravariant=True) \
# E: TypeVar cannot be both covariant and contravariant
[builtins fixtures/bool.pyi]

[case testInvalidTypevarArgumentsGenericConstraint]
from typing import Generic, List, TypeVar

T = TypeVar("T")

Bad1 = TypeVar("Bad1", int, T) # E: TypeVar constraint type cannot be parametrized by type variables
Bad2 = TypeVar("Bad2", int, List[T]) # E: TypeVar constraint type cannot be parametrized by type variables
Bad3 = TypeVar("Bad3", int, List[List[T]]) # E: TypeVar constraint type cannot be parametrized by type variables
def f(x: T) -> None:
Bad4 = TypeVar("Bad4", int, List[T]) # E: TypeVar constraint type cannot be parametrized by type variables
class C(Generic[T]):
Bad5 = TypeVar("Bad5", int, List[T]) # E: TypeVar constraint type cannot be parametrized by type variables

[case testInvalidTypevarValues]
from typing import TypeVar
b = TypeVar('b', *[int]) # E: Unexpected argument to "TypeVar()"
Expand Down

0 comments on commit 8410966

Please sign in to comment.