Skip to content

Commit e378601

Browse files
aisiposJukkaL
authored andcommitted
Implement a reveal_locals expression (#3425)
`reveal_locals()` is a magic function that causes mypy to display the types of all variables in the current local scope, analogous to `reveal_type(e)`.
1 parent 4e25c67 commit e378601

16 files changed

+191
-46
lines changed

docs/source/common_issues.rst

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,12 +430,25 @@ understand how mypy handles a particular piece of code. Example:
430430
431431
reveal_type((1, 'hello')) # Revealed type is 'Tuple[builtins.int, builtins.str]'
432432
433+
You can also use ``reveal_locals()`` at any line in a file
434+
to see the types of all local varaibles at once. Example:
435+
436+
.. code-block:: python
437+
438+
a = 1
439+
b = 'one'
440+
reveal_locals()
441+
# Revealed local types are:
442+
# a: builtins.int
443+
# b: builtins.str
433444
.. note::
434445

435-
``reveal_type`` is only understood by mypy and doesn't exist
436-
in Python, if you try to run your program. You'll have to remove
437-
any ``reveal_type`` calls before you can run your code.
438-
``reveal_type`` is always available and you don't need to import it.
446+
``reveal_type`` and ``reveal_locals`` are only understood by mypy and
447+
don't exist in Python. If you try to run your program, you'll have to
448+
remove any ``reveal_type`` and ``reveal_locals`` calls before you can
449+
run your code. Both are always available and you don't need to import
450+
them.
451+
439452

440453
.. _import-cycles:
441454

mypy/checkexpr.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
from mypy.nodes import (
1616
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
1717
MemberExpr, IntExpr, StrExpr, BytesExpr, UnicodeExpr, FloatExpr,
18-
OpExpr, UnaryExpr, IndexExpr, CastExpr, RevealTypeExpr, TypeApplication, ListExpr,
18+
OpExpr, UnaryExpr, IndexExpr, CastExpr, RevealExpr, TypeApplication, ListExpr,
1919
TupleExpr, DictExpr, LambdaExpr, SuperExpr, SliceExpr, Context, Expression,
2020
ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator,
2121
ConditionalExpr, ComparisonExpr, TempNode, SetComprehension,
2222
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
2323
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
2424
TypeAliasExpr, BackquoteExpr, EnumCallExpr,
25-
ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE,
25+
ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE
2626
)
2727
from mypy.literals import literal
2828
from mypy import nodes
@@ -1802,14 +1802,29 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
18021802
context=expr)
18031803
return target_type
18041804

1805-
def visit_reveal_type_expr(self, expr: RevealTypeExpr) -> Type:
1805+
def visit_reveal_expr(self, expr: RevealExpr) -> Type:
18061806
"""Type check a reveal_type expression."""
1807-
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
1808-
if not self.chk.current_node_deferred:
1809-
self.msg.reveal_type(revealed_type, expr)
1810-
if not self.chk.in_checked_function():
1811-
self.msg.note("'reveal_type' always outputs 'Any' in unchecked functions", expr)
1812-
return revealed_type
1807+
if expr.kind == REVEAL_TYPE:
1808+
assert expr.expr is not None
1809+
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
1810+
if not self.chk.current_node_deferred:
1811+
self.msg.reveal_type(revealed_type, expr)
1812+
if not self.chk.in_checked_function():
1813+
self.msg.note("'reveal_type' always outputs 'Any' in unchecked functions",
1814+
expr)
1815+
return revealed_type
1816+
else:
1817+
# REVEAL_LOCALS
1818+
if not self.chk.current_node_deferred:
1819+
# the RevealExpr contains a local_nodes attribute,
1820+
# calculated at semantic analysis time. Use it to pull out the
1821+
# corresponding subset of variables in self.chk.type_map
1822+
names_to_types = {
1823+
var_node.name(): var_node.type for var_node in expr.local_nodes
1824+
} if expr.local_nodes is not None else {}
1825+
1826+
self.msg.reveal_locals(names_to_types, expr)
1827+
return NoneTyp()
18131828

18141829
def visit_type_application(self, tapp: TypeApplication) -> Type:
18151830
"""Type check a type application (expr[type, ...])."""

mypy/literals.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Expression, ComparisonExpr, OpExpr, MemberExpr, UnaryExpr, StarExpr, IndexExpr, LITERAL_YES,
55
LITERAL_NO, NameExpr, LITERAL_TYPE, IntExpr, FloatExpr, ComplexExpr, StrExpr, BytesExpr,
66
UnicodeExpr, ListExpr, TupleExpr, SetExpr, DictExpr, CallExpr, SliceExpr, CastExpr,
7-
ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealTypeExpr, SuperExpr,
7+
ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealExpr, SuperExpr,
88
TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension,
99
GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr,
1010
TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode,
@@ -175,7 +175,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> None:
175175
def visit_yield_expr(self, e: YieldExpr) -> None:
176176
return None
177177

178-
def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None:
178+
def visit_reveal_expr(self, e: RevealExpr) -> None:
179179
return None
180180

181181
def visit_super_expr(self, e: SuperExpr) -> None:

mypy/messages.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
checker but we are moving away from this convention.
1111
"""
1212

13+
from collections import OrderedDict
1314
import re
1415
import difflib
1516

@@ -26,10 +27,9 @@
2627
TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases,
2728
ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2,
2829
ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode,
29-
CallExpr
30+
CallExpr, Expression
3031
)
3132

32-
3333
# Constants that represent simple type checker error message, i.e. messages
3434
# that do not have any parameters.
3535

@@ -970,6 +970,16 @@ def invalid_signature(self, func_type: Type, context: Context) -> None:
970970
def reveal_type(self, typ: Type, context: Context) -> None:
971971
self.fail('Revealed type is \'{}\''.format(typ), context)
972972

973+
def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) -> None:
974+
# To ensure that the output is predictable on Python < 3.6,
975+
# use an ordered dictionary sorted by variable name
976+
sorted_locals = OrderedDict(sorted(type_map.items(), key=lambda t: t[0]))
977+
self.fail("Revealed local types are:", context)
978+
# Note that self.fail does a strip() on the message, so we cannot prepend with spaces
979+
# for indentation
980+
for line in ['{}: {}'.format(k, v) for k, v in sorted_locals.items()]:
981+
self.fail(line, context)
982+
973983
def unsupported_type_type(self, item: Type, context: Context) -> None:
974984
self.fail('Unsupported type Type[{}]'.format(self.format(item)), context)
975985

mypy/nodes.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ def get_column(self) -> int:
7878
# XXX what?
7979
UNBOUND_IMPORTED = 7 # type: int
8080

81+
# RevealExpr node kinds
82+
REVEAL_TYPE = 0 # type: int
83+
REVEAL_LOCALS = 1 # type: int
8184

8285
LITERAL_YES = 2
8386
LITERAL_TYPE = 1
@@ -1595,17 +1598,24 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
15951598
return visitor.visit_cast_expr(self)
15961599

15971600

1598-
class RevealTypeExpr(Expression):
1599-
"""Reveal type expression reveal_type(expr)."""
1601+
class RevealExpr(Expression):
1602+
"""Reveal type expression reveal_type(expr) or reveal_locals() expression."""
16001603

1601-
expr = None # type: Expression
1604+
expr = None # type: Optional[Expression]
1605+
kind = 0 # type: int
1606+
local_nodes = None # type: Optional[List[Var]]
16021607

1603-
def __init__(self, expr: Expression) -> None:
1608+
def __init__(
1609+
self, kind: int,
1610+
expr: Optional[Expression] = None,
1611+
local_nodes: 'Optional[List[Var]]' = None) -> None:
16041612
super().__init__()
16051613
self.expr = expr
1614+
self.kind = kind
1615+
self.local_nodes = local_nodes
16061616

16071617
def accept(self, visitor: ExpressionVisitor[T]) -> T:
1608-
return visitor.visit_reveal_type_expr(self)
1618+
return visitor.visit_reveal_expr(self)
16091619

16101620

16111621
class SuperExpr(Expression):

mypy/semanal.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
RaiseStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt,
4747
ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt, PassStmt,
4848
GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr,
49-
SliceExpr, CastExpr, RevealTypeExpr, TypeApplication, Context, SymbolTable,
49+
SliceExpr, CastExpr, RevealExpr, TypeApplication, Context, SymbolTable,
5050
SymbolTableNode, TVAR, ListComprehension, GeneratorExpr,
5151
LambdaExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, NewTypeExpr,
5252
StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr,
@@ -56,7 +56,7 @@
5656
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
5757
IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, EnumCallExpr, ImportedName,
5858
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ARG_OPT, nongen_builtins,
59-
collections_type_aliases, get_member_expr_fullname,
59+
collections_type_aliases, get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS
6060
)
6161
from mypy.literals import literal
6262
from mypy.tvar_scope import TypeVarScope
@@ -2574,7 +2574,38 @@ def visit_call_expr(self, expr: CallExpr) -> None:
25742574
elif refers_to_fullname(expr.callee, 'builtins.reveal_type'):
25752575
if not self.check_fixed_args(expr, 1, 'reveal_type'):
25762576
return
2577-
expr.analyzed = RevealTypeExpr(expr.args[0])
2577+
expr.analyzed = RevealExpr(kind=REVEAL_TYPE, expr=expr.args[0])
2578+
expr.analyzed.line = expr.line
2579+
expr.analyzed.column = expr.column
2580+
expr.analyzed.accept(self)
2581+
elif refers_to_fullname(expr.callee, 'builtins.reveal_locals'):
2582+
# Store the local variable names into the RevealExpr for use in the
2583+
# type checking pass
2584+
local_nodes = [] # type: List[Var]
2585+
if self.is_module_scope():
2586+
# try to determine just the variable declarations in module scope
2587+
# self.globals.values() contains SymbolTableNode's
2588+
# Each SymbolTableNode has an attribute node that is nodes.Var
2589+
# look for variable nodes that marked as is_inferred
2590+
# Each symboltable node has a Var node as .node
2591+
local_nodes = cast(
2592+
List[Var],
2593+
[
2594+
n.node for name, n in self.globals.items()
2595+
if getattr(n.node, 'is_inferred', False)
2596+
]
2597+
)
2598+
elif self.is_class_scope():
2599+
# type = None # type: Optional[TypeInfo]
2600+
if self.type is not None:
2601+
local_nodes = cast(List[Var], [st.node for st in self.type.names.values()])
2602+
elif self.is_func_scope():
2603+
# locals = None # type: List[Optional[SymbolTable]]
2604+
if self.locals is not None:
2605+
symbol_table = self.locals[-1]
2606+
if symbol_table is not None:
2607+
local_nodes = cast(List[Var], [st.node for st in symbol_table.values()])
2608+
expr.analyzed = RevealExpr(kind=REVEAL_LOCALS, local_nodes=local_nodes)
25782609
expr.analyzed.line = expr.line
25792610
expr.analyzed.column = expr.column
25802611
expr.analyzed.accept(self)
@@ -2826,8 +2857,14 @@ def visit_cast_expr(self, expr: CastExpr) -> None:
28262857
expr.expr.accept(self)
28272858
expr.type = self.anal_type(expr.type)
28282859

2829-
def visit_reveal_type_expr(self, expr: RevealTypeExpr) -> None:
2830-
expr.expr.accept(self)
2860+
def visit_reveal_expr(self, expr: RevealExpr) -> None:
2861+
if expr.kind == REVEAL_TYPE:
2862+
if expr.expr is not None:
2863+
expr.expr.accept(self)
2864+
else:
2865+
# Reveal locals doesn't have an inner expression, there's no
2866+
# need to traverse inside it
2867+
pass
28312868

28322869
def visit_type_application(self, expr: TypeApplication) -> None:
28332870
expr.expr.accept(self)

mypy/semanal_pass1.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -
103103
# reveal_type is a mypy-only function that gives an error with
104104
# the type of its arg.
105105
('reveal_type', AnyType(TypeOfAny.special_form)),
106+
# reveal_locals is a mypy-only function that gives an error with the types of
107+
# locals
108+
('reveal_locals', AnyType(TypeOfAny.special_form)),
106109
] # type: List[Tuple[str, Type]]
107110

108111
# TODO(ddfisher): This guard is only needed because mypy defines

mypy/semanal_pass3.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef,
1919
Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr,
2020
CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue,
21-
TupleExpr, RevealTypeExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef,
21+
TupleExpr, RevealExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef,
2222
MDEF,
2323
)
2424
from mypy.types import (
@@ -278,8 +278,8 @@ def visit_cast_expr(self, e: CastExpr) -> None:
278278
self.analyze(e.type, e)
279279
super().visit_cast_expr(e)
280280

281-
def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None:
282-
super().visit_reveal_type_expr(e)
281+
def visit_reveal_expr(self, e: RevealExpr) -> None:
282+
super().visit_reveal_expr(e)
283283

284284
def visit_type_application(self, e: TypeApplication) -> None:
285285
for type in e.types:

mypy/server/subexpr.py

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

55
from mypy.nodes import (
66
Expression, Node, MemberExpr, YieldFromExpr, YieldExpr, CallExpr, OpExpr, ComparisonExpr,
7-
SliceExpr, CastExpr, RevealTypeExpr, UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr,
7+
SliceExpr, CastExpr, RevealExpr, UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr,
88
IndexExpr, GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension,
99
ConditionalExpr, TypeApplication, LambdaExpr, StarExpr, BackquoteExpr, AwaitExpr,
1010
)
@@ -72,9 +72,9 @@ def visit_cast_expr(self, e: CastExpr) -> None:
7272
self.add(e)
7373
super().visit_cast_expr(e)
7474

75-
def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None:
75+
def visit_reveal_expr(self, e: RevealExpr) -> None:
7676
self.add(e)
77-
super().visit_reveal_type_expr(e)
77+
super().visit_reveal_expr(e)
7878

7979
def visit_unary_expr(self, e: UnaryExpr) -> None:
8080
self.add(e)

mypy/strconv.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,12 @@ def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> str:
406406
def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> str:
407407
return self.dump([o.expr, o.type], o)
408408

409-
def visit_reveal_type_expr(self, o: 'mypy.nodes.RevealTypeExpr') -> str:
410-
return self.dump([o.expr], o)
409+
def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> str:
410+
if o.kind == mypy.nodes.REVEAL_TYPE:
411+
return self.dump([o.expr], o)
412+
else:
413+
# REVEAL_LOCALS
414+
return self.dump([o.local_nodes], o)
411415

412416
def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> str:
413417
return self.dump([o.op, o.expr], o)

mypy/traverser.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
Block, MypyFile, FuncItem, CallExpr, ClassDef, Decorator, FuncDef,
66
ExpressionStmt, AssignmentStmt, OperatorAssignmentStmt, WhileStmt,
77
ForStmt, ReturnStmt, AssertStmt, DelStmt, IfStmt, RaiseStmt,
8-
TryStmt, WithStmt, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealTypeExpr,
8+
TryStmt, WithStmt, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealExpr,
99
UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr, IndexExpr,
1010
GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension,
1111
ConditionalExpr, TypeApplication, ExecStmt, Import, ImportFrom,
1212
LambdaExpr, ComparisonExpr, OverloadedFuncDef, YieldFromExpr,
13-
YieldExpr, StarExpr, BackquoteExpr, AwaitExpr, PrintStmt, SuperExpr,
13+
YieldExpr, StarExpr, BackquoteExpr, AwaitExpr, PrintStmt, SuperExpr, REVEAL_TYPE
1414
)
1515

1616

@@ -181,8 +181,13 @@ def visit_slice_expr(self, o: SliceExpr) -> None:
181181
def visit_cast_expr(self, o: CastExpr) -> None:
182182
o.expr.accept(self)
183183

184-
def visit_reveal_type_expr(self, o: RevealTypeExpr) -> None:
185-
o.expr.accept(self)
184+
def visit_reveal_expr(self, o: RevealExpr) -> None:
185+
if o.kind == REVEAL_TYPE:
186+
assert o.expr is not None
187+
o.expr.accept(self)
188+
else:
189+
# RevealLocalsExpr doesn't have an inner expression
190+
pass
186191

187192
def visit_unary_expr(self, o: UnaryExpr) -> None:
188193
o.expr.accept(self)

mypy/treetransform.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
OperatorAssignmentStmt, ExpressionStmt, AssignmentStmt, ReturnStmt,
1212
RaiseStmt, AssertStmt, DelStmt, BreakStmt, ContinueStmt,
1313
PassStmt, GlobalDecl, WhileStmt, ForStmt, IfStmt, TryStmt, WithStmt,
14-
CastExpr, RevealTypeExpr, TupleExpr, GeneratorExpr, ListComprehension, ListExpr,
14+
CastExpr, RevealExpr, TupleExpr, GeneratorExpr, ListComprehension, ListExpr,
1515
ConditionalExpr, DictExpr, SetExpr, NameExpr, IntExpr, StrExpr, BytesExpr,
1616
UnicodeExpr, FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr,
1717
SliceExpr, OpExpr, UnaryExpr, LambdaExpr, TypeApplication, PrintStmt,
@@ -20,7 +20,7 @@
2020
YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension,
2121
DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr,
2222
YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr,
23-
OverloadPart, EnumCallExpr,
23+
OverloadPart, EnumCallExpr, REVEAL_TYPE
2424
)
2525
from mypy.types import Type, FunctionLike
2626
from mypy.traverser import TraverserVisitor
@@ -377,8 +377,13 @@ def visit_cast_expr(self, node: CastExpr) -> CastExpr:
377377
return CastExpr(self.expr(node.expr),
378378
self.type(node.type))
379379

380-
def visit_reveal_type_expr(self, node: RevealTypeExpr) -> RevealTypeExpr:
381-
return RevealTypeExpr(self.expr(node.expr))
380+
def visit_reveal_expr(self, node: RevealExpr) -> RevealExpr:
381+
if node.kind == REVEAL_TYPE:
382+
assert node.expr is not None
383+
return RevealExpr(kind=REVEAL_TYPE, expr=self.expr(node.expr))
384+
else:
385+
# Reveal locals expressions don't have any sub expressions
386+
return node
382387

383388
def visit_super_expr(self, node: SuperExpr) -> SuperExpr:
384389
call = self.expr(node.call)

0 commit comments

Comments
 (0)