Skip to content

Commit 2f94f22

Browse files
committed
WIP: Check for unbound type variables in expression types
This breaks some of the tests (apparently due to existing mypy bugs). Also, this linting should likely be off by default, but turned on when running the tests.
1 parent 3acdf54 commit 2f94f22

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

mypy/checker.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from mypy.types import (
3131
Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType,
3232
Instance, NoneTyp, ErrorType, strip_type,
33-
UnionType, TypeVarType, PartialType, DeletedType
33+
UnionType, TypeVarType, PartialType, DeletedType,
34+
TypeQuery, ANY_TYPE_STRATEGY
3435
)
3536
from mypy.sametypes import is_same_type
3637
from mypy.messages import MessageBuilder
@@ -353,6 +354,9 @@ class TypeChecker(NodeVisitor[Type]):
353354
dynamic_funcs = None # type: List[bool]
354355
# Stack of functions being type checked
355356
function_stack = None # type: List[FuncItem]
357+
# Bound type variables (type parameters of surrounding generic
358+
# classes and functions)
359+
bound_tvars = set() # type: Set[int]
356360
# Set to True on return/break/raise, False on blocks that can block any of them
357361
breaking_out = False
358362
# Do weak type checking in this file
@@ -396,6 +400,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
396400
self.type_context = []
397401
self.dynamic_funcs = []
398402
self.function_stack = []
403+
self.bound_tvars = set() # type: Set[int]
399404
self.weak_opts = set() # type: Set[str]
400405
self.partial_types = []
401406
self.deferred_nodes = []
@@ -458,6 +463,10 @@ def accept(self, node: Node, type_context: Type = None) -> Type:
458463
self.type_context.append(type_context)
459464
try:
460465
typ = node.accept(self)
466+
if not only_has_type_vars(typ, self.bound_tvars):
467+
# This is really an internal mypy error, not a type error.
468+
# But it's more convenient to let mypy continue...
469+
self.fail('Unexpected free type variable in {}'.format(typ), node)
461470
except Exception as err:
462471
report_internal_error(err, self.errors.file, node.line)
463472
self.type_context.pop()
@@ -648,6 +657,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None:
648657
old_binder = self.binder
649658
self.binder = ConditionalTypeBinder()
650659
self.binder.push_frame()
660+
self.bound_tvars |= set(typ.type_var_ids())
651661
defn.expanded.append(item)
652662

653663
# We may be checking a function definition or an anonymous
@@ -740,6 +750,7 @@ def is_implicit_any(t: Type) -> bool:
740750

741751
self.return_types.pop()
742752

753+
self.bound_tvars -= set(typ.type_var_ids())
743754
self.binder = old_binder
744755

745756
def check_reverse_op_method(self, defn: FuncItem, typ: CallableType,
@@ -1034,7 +1045,9 @@ def visit_class_def(self, defn: ClassDef) -> Type:
10341045
old_binder = self.binder
10351046
self.binder = ConditionalTypeBinder()
10361047
self.binder.push_frame()
1048+
self.bound_tvars |= set(tv.id for tv in defn.type_vars)
10371049
self.accept(defn.defs)
1050+
self.bound_tvars -= set(tv.id for tv in defn.type_vars)
10381051
self.binder = old_binder
10391052
self.check_multiple_inheritance(typ)
10401053
self.leave_partial_types()
@@ -2549,3 +2562,25 @@ def is_valid_inferred_type(typ: Type) -> bool:
25492562
if not is_valid_inferred_type(item):
25502563
return False
25512564
return True
2565+
2566+
2567+
def only_has_type_vars(typ: Type, allowed_tvars: Set[int]) -> bool:
2568+
if typ is None:
2569+
return True
2570+
return not typ.accept(UnboundTypeVarVisitor(allowed_tvars))
2571+
2572+
2573+
class UnboundTypeVarVisitor(TypeQuery):
2574+
allowed_tvars = set() # type: Set[int]
2575+
2576+
def __init__(self, allowed_tvars: Set[int]) -> None:
2577+
super().__init__(False, ANY_TYPE_STRATEGY)
2578+
self.allowed_tvars = allowed_tvars
2579+
2580+
def visit_type_var(self, t: TypeVarType) -> bool:
2581+
return t.id not in self.allowed_tvars
2582+
2583+
def visit_callable_type(self, t: CallableType) -> bool:
2584+
# Bind the generic type variables of t
2585+
new_visitor = UnboundTypeVarVisitor(self.allowed_tvars | set(t.type_var_ids()))
2586+
return new_visitor.query_types(t.arg_types + [t.ret_type])

mypy/expandtype.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ def visit_type_var(self, t: TypeVarType) -> Type:
7575

7676
def visit_callable_type(self, t: CallableType) -> Type:
7777
return t.copy_modified(arg_types=self.expand_types(t.arg_types),
78-
ret_type=t.ret_type.accept(self))
78+
ret_type=t.ret_type.accept(self),
79+
variables=[var for var in t.variables
80+
if var.id not in self.variables])
7981

8082
def visit_overloaded(self, t: Overloaded) -> Type:
8183
items = [] # type: List[CallableType]

0 commit comments

Comments
 (0)