Skip to content

Commit 081532d

Browse files
ecpriceJukkaL
authored andcommitted
Deleted types
Add DeletedType. Support local try/except variables. Improvements to error messages for deleted types. Also remove some useless code. I wrote this code during hack week, but I don't think it serves any purpose: I was just confused.
1 parent 6a40a60 commit 081532d

14 files changed

+199
-25
lines changed

mypy/checker.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from mypy.types import (
3030
Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType,
3131
Instance, NoneTyp, UnboundType, ErrorType, TypeTranslator, strip_type,
32-
UnionType, TypeVarType, PartialType
32+
UnionType, TypeVarType, PartialType, DeletedType
3333
)
3434
from mypy.sametypes import is_same_type
3535
from mypy.messages import MessageBuilder
@@ -126,6 +126,13 @@ def push(self, expr: Node, type: Type) -> None:
126126
def get(self, expr: Node) -> Type:
127127
return self._get(expr.literal_hash)
128128

129+
def cleanse(self, expr: Node) -> None:
130+
"""Remove all references to a Node from the binder."""
131+
key = expr.literal_hash
132+
for frame in self.frames:
133+
if key in frame:
134+
del frame[key]
135+
129136
def update_from_options(self, frames: List[Frame]) -> bool:
130137
"""Update the frame to reflect that each key will be updated
131138
as in one of the frames. Return whether any item changes.
@@ -973,16 +980,10 @@ def check_import(self, node: ImportBase) -> Type:
973980
def visit_block(self, b: Block) -> Type:
974981
if b.is_unreachable:
975982
return None
976-
n_frames_added = 0
977983
for s in b.body:
978-
if self.typing_mode_weak() and isinstance(s, AssignmentStmt):
979-
self.binder.push_frame()
980-
n_frames_added += 1
981984
self.accept(s)
982985
if self.breaking_out:
983986
break
984-
for i in range(n_frames_added):
985-
self.binder.pop_frame(False, True)
986987

987988
def visit_assignment_stmt(self, s: AssignmentStmt) -> Type:
988989
"""Type check an assignment statement.
@@ -1250,6 +1251,8 @@ def infer_variable_type(self, name: Var, lvalue: Node,
12501251
elif isinstance(init_type, Void):
12511252
self.check_not_void(init_type, context)
12521253
self.set_inference_error_fallback_type(name, lvalue, init_type, context)
1254+
elif isinstance(init_type, DeletedType):
1255+
self.msg.deleted_as_rvalue(init_type, context)
12531256
elif not is_valid_inferred_type(init_type):
12541257
# We cannot use the type of the initialization expression for full type
12551258
# inference (it's not specific enough), but we might be able to give
@@ -1324,11 +1327,16 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Node,
13241327
return AnyType()
13251328
else:
13261329
rvalue_type = self.accept(rvalue, lvalue_type)
1330+
if isinstance(rvalue_type, DeletedType):
1331+
self.msg.deleted_as_rvalue(rvalue_type, context)
13271332
if self.typing_mode_weak():
13281333
return rvalue_type
1329-
self.check_subtype(rvalue_type, lvalue_type, context, msg,
1330-
'{} has type'.format(rvalue_name),
1331-
'{} has type'.format(lvalue_name))
1334+
if isinstance(lvalue_type, DeletedType):
1335+
self.msg.deleted_as_lvalue(lvalue_type, context)
1336+
else:
1337+
self.check_subtype(rvalue_type, lvalue_type, context, msg,
1338+
'{} has type'.format(rvalue_name),
1339+
'{} has type'.format(lvalue_name))
13321340
return rvalue_type
13331341

13341342
def check_indexed_assignment(self, lvalue: IndexExpr,
@@ -1638,13 +1646,31 @@ def visit_try_stmt(self, s: TryStmt) -> Type:
16381646
completed_frames.append(frame_on_completion)
16391647

16401648
for i in range(len(s.handlers)):
1649+
self.binder.push_frame()
16411650
if s.types[i]:
16421651
t = self.exception_type(s.types[i])
16431652
if s.vars[i]:
1644-
self.check_assignment(s.vars[i],
1645-
self.temp_node(t, s.vars[i]))
1646-
self.binder.push_frame()
1653+
# To support local variables, we make this a definition line,
1654+
# causing assignment to set the variable's type.
1655+
s.vars[i].is_def = True
1656+
self.check_assignment(s.vars[i], self.temp_node(t, s.vars[i]))
16471657
self.accept(s.handlers[i])
1658+
if s.vars[i]:
1659+
# Exception variables are deleted in python 3 but not python 2.
1660+
# But, since it's bad form in python 2 and the type checking
1661+
# wouldn't work very well, we delete it anyway.
1662+
1663+
# Unfortunately, this doesn't let us detect usage before the
1664+
# try/except block.
1665+
if self.pyversion[0] >= 3:
1666+
source = s.vars[i].name
1667+
else:
1668+
source = ('(exception variable "{}", which we do not accept '
1669+
'outside except: blocks even in python 2)'.format(s.vars[i].name))
1670+
var = cast(Var, s.vars[i].node)
1671+
var.type = DeletedType(source=source)
1672+
self.binder.cleanse(s.vars[i])
1673+
16481674
self.breaking_out = False
16491675
changed, frame_on_completion = self.binder.pop_frame()
16501676
completed_frames.append(frame_on_completion)
@@ -1745,7 +1771,19 @@ def visit_del_stmt(self, s: DelStmt) -> Type:
17451771
c.line = s.line
17461772
return c.accept(self)
17471773
else:
1774+
def flatten(t: Node) -> List[Node]:
1775+
"""Flatten a nested sequence of tuples/lists into one list of nodes."""
1776+
if isinstance(t, TupleExpr) or isinstance(t, ListExpr):
1777+
t = cast(Union[TupleExpr, ListExpr], t)
1778+
return [b for a in t.items for b in flatten(a)]
1779+
else:
1780+
return [t]
1781+
17481782
s.expr.accept(self)
1783+
for elt in flatten(s.expr):
1784+
if isinstance(elt, NameExpr):
1785+
self.binder.assign_type(elt, DeletedType(source=elt.name),
1786+
self.typing_mode_weak())
17491787
return None
17501788

17511789
def visit_decorator(self, e: Decorator) -> Type:

mypy/checkexpr.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mypy.types import (
66
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
77
TupleType, Instance, TypeVarType, TypeTranslator, ErasedType, FunctionLike, UnionType,
8-
PartialType
8+
PartialType, DeletedType
99
)
1010
from mypy.nodes import (
1111
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
@@ -568,6 +568,8 @@ def check_arg(self, caller_type: Type, original_caller_type: Type,
568568
"""Check the type of a single argument in a call."""
569569
if isinstance(caller_type, Void):
570570
messages.does_not_return_value(caller_type, context)
571+
elif isinstance(caller_type, DeletedType):
572+
messages.deleted_as_rvalue(caller_type, context)
571573
elif not is_subtype(caller_type, callee_type):
572574
messages.incompatible_argument(n, m, callee, original_caller_type,
573575
context)

mypy/checkmember.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from mypy.types import (
66
Type, Instance, AnyType, TupleType, CallableType, FunctionLike, TypeVarDef,
7-
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType
7+
Overloaded, TypeVarType, TypeTranslator, UnionType, PartialType, DeletedType
88
)
99
from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context
1010
from mypy.nodes import ARG_POS, function_type, Decorator, OverloadedFuncDef
@@ -99,6 +99,9 @@ def analyze_member_access(name: str, typ: Type, node: Context, is_lvalue: bool,
9999
elif isinstance(typ, TypeVarType):
100100
return analyze_member_access(name, typ.upper_bound, node, is_lvalue, is_super,
101101
builtin_type, msg, report_type=report_type)
102+
elif isinstance(typ, DeletedType):
103+
msg.deleted_as_rvalue(typ, node)
104+
return AnyType()
102105
return msg.has_no_attr(report_type, name, node)
103106

104107

mypy/constraints.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from mypy.types import (
66
CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType,
7-
Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, is_named_instance
7+
Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType,
8+
is_named_instance
89
)
910
from mypy.expandtype import expand_caller_var_args
1011
from mypy.maptype import map_instance_to_supertype
@@ -151,6 +152,9 @@ def visit_none_type(self, template: NoneTyp) -> List[Constraint]:
151152
def visit_erased_type(self, template: ErasedType) -> List[Constraint]:
152153
return []
153154

155+
def visit_deleted_type(self, template: DeletedType) -> List[Constraint]:
156+
return []
157+
154158
# Errors
155159

156160
def visit_partial_type(self, template: PartialType) -> List[Constraint]:

mypy/erasetype.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from mypy.types import (
44
Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp,
55
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType,
6-
TypeTranslator, TypeList, PartialType
6+
PartialType, DeletedType, TypeTranslator, TypeList
77
)
88

99

@@ -50,6 +50,9 @@ def visit_partial_type(self, t: PartialType) -> Type:
5050
# Should not get here.
5151
raise RuntimeError()
5252

53+
def visit_deleted_type(self, t: DeletedType) -> Type:
54+
return t
55+
5356
def visit_instance(self, t: Instance) -> Type:
5457
return Instance(t.type, [AnyType()] * len(t.args), t.line)
5558

mypy/expandtype.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from mypy.types import (
44
Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType,
55
Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList,
6-
PartialType
6+
PartialType, DeletedType
77
)
88

99

@@ -61,6 +61,9 @@ def visit_void(self, t: Void) -> Type:
6161
def visit_none_type(self, t: NoneTyp) -> Type:
6262
return t
6363

64+
def visit_deleted_type(self, t: DeletedType) -> Type:
65+
return t
66+
6467
def visit_erased_type(self, t: ErasedType) -> Type:
6568
# Should not get here.
6669
raise RuntimeError()

mypy/join.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mypy.types import (
66
Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType,
77
ErrorType, TypeVarType, CallableType, TupleType, ErasedType, TypeList,
8-
UnionType, FunctionLike, Overloaded, PartialType
8+
UnionType, FunctionLike, Overloaded, PartialType, DeletedType
99
)
1010
from mypy.maptype import map_instance_to_supertype
1111
from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars
@@ -110,6 +110,12 @@ def visit_none_type(self, t: NoneTyp) -> Type:
110110
else:
111111
return self.default(self.s)
112112

113+
def visit_deleted_type(self, t: DeletedType) -> Type:
114+
if not isinstance(self.s, Void):
115+
return self.s
116+
else:
117+
return self.default(self.s)
118+
113119
def visit_erased_type(self, t: ErasedType) -> Type:
114120
return self.s
115121

mypy/meet.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from mypy.join import is_similar_callables, combine_similar_callables
44
from mypy.types import (
55
Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVarType,
6-
Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType
6+
Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType
77
)
88
from mypy.sametypes import is_same_type
99
from mypy.subtypes import is_subtype
@@ -118,6 +118,15 @@ def visit_none_type(self, t: NoneTyp) -> Type:
118118
else:
119119
return ErrorType()
120120

121+
def visit_deleted_type(self, t: DeletedType) -> Type:
122+
if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType):
123+
if isinstance(self.s, NoneTyp):
124+
return self.s
125+
else:
126+
return t
127+
else:
128+
return ErrorType()
129+
121130
def visit_erased_type(self, t: ErasedType) -> Type:
122131
return self.s
123132

mypy/messages.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from mypy.errors import Errors
1212
from mypy.types import (
1313
Type, CallableType, Instance, TypeVarType, TupleType, UnionType, Void, NoneTyp, AnyType,
14-
Overloaded, FunctionLike
14+
Overloaded, FunctionLike, DeletedType,
1515
)
1616
from mypy.nodes import TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases
1717

@@ -247,6 +247,8 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str:
247247
return 'None'
248248
elif isinstance(typ, AnyType):
249249
return '"Any"'
250+
elif isinstance(typ, DeletedType):
251+
return '<deleted>'
250252
elif typ is None:
251253
raise RuntimeError('Type is None')
252254
else:
@@ -520,6 +522,26 @@ def does_not_return_value(self, void_type: Type, context: Context) -> None:
520522
self.fail('{} does not return a value'.format(
521523
capitalize((cast(Void, void_type)).source)), context)
522524

525+
def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None:
526+
"""Report an error about using an deleted type as an rvalue."""
527+
if typ.source is None:
528+
s = ""
529+
else:
530+
s = " '{}'".format(typ.source)
531+
self.fail('Trying to read deleted variable{}'.format(s), context)
532+
533+
def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None:
534+
"""Report an error about using an deleted type as an lvalue.
535+
536+
Currently, this only occurs when trying to assign to an
537+
exception variable outside the local except: blocks.
538+
"""
539+
if typ.source is None:
540+
s = ""
541+
else:
542+
s = " '{}'".format(typ.source)
543+
self.fail('Assignment to variable{} outside except: block'.format(s), context)
544+
523545
def no_variant_matches_arguments(self, overload: Overloaded, arg_types: List[Type],
524546
context: Context) -> None:
525547
if overload.name():

mypy/sametypes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from mypy.types import (
44
Type, UnboundType, ErrorType, AnyType, NoneTyp, Void, TupleType, UnionType, CallableType,
5-
TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType
5+
TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType
66
)
77

88

@@ -57,6 +57,9 @@ def visit_erased_type(self, left: ErasedType) -> bool:
5757
# Should not get here.
5858
raise RuntimeError()
5959

60+
def visit_deleted_type(self, left: DeletedType) -> bool:
61+
return isinstance(self.right, DeletedType)
62+
6063
def visit_instance(self, left: Instance) -> bool:
6164
return (isinstance(self.right, Instance) and
6265
left.type == (cast(Instance, self.right)).type and

mypy/subtypes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from mypy.types import (
44
Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp,
55
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, TypeList,
6-
PartialType, is_named_instance
6+
PartialType, DeletedType, is_named_instance
77
)
88
import mypy.applytype
99
import mypy.constraints
@@ -91,6 +91,9 @@ def visit_none_type(self, left: NoneTyp) -> bool:
9191
def visit_erased_type(self, left: ErasedType) -> bool:
9292
return True
9393

94+
def visit_deleted_type(self, left: DeletedType) -> bool:
95+
return True
96+
9497
def visit_instance(self, left: Instance) -> bool:
9598
right = self.right
9699
if isinstance(right, Instance):

0 commit comments

Comments
 (0)