Skip to content

Implement a reveal_locals expression. (#3387) #3425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions docs/source/common_issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -430,12 +430,25 @@ understand how mypy handles a particular piece of code. Example:

reveal_type((1, 'hello')) # Revealed type is 'Tuple[builtins.int, builtins.str]'

You can also use ``reveal_locals()`` at any line in a file
to see the types of all local varaibles at once. Example:

.. code-block:: python

a = 1
b = 'one'
reveal_locals()
# Revealed local types are:
# a: builtins.int
# b: builtins.str
.. note::

``reveal_type`` is only understood by mypy and doesn't exist
in Python, if you try to run your program. You'll have to remove
any ``reveal_type`` calls before you can run your code.
``reveal_type`` is always available and you don't need to import it.
``reveal_type`` and ``reveal_locals`` are only understood by mypy and
don't exist in Python. If you try to run your program, you'll have to
remove any ``reveal_type`` and ``reveal_locals`` calls before you can
run your code. Both are always available and you don't need to import
them.


.. _import-cycles:

Expand Down
33 changes: 24 additions & 9 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
from mypy.nodes import (
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
MemberExpr, IntExpr, StrExpr, BytesExpr, UnicodeExpr, FloatExpr,
OpExpr, UnaryExpr, IndexExpr, CastExpr, RevealTypeExpr, TypeApplication, ListExpr,
OpExpr, UnaryExpr, IndexExpr, CastExpr, RevealExpr, TypeApplication, ListExpr,
TupleExpr, DictExpr, LambdaExpr, SuperExpr, SliceExpr, Context, Expression,
ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator,
ConditionalExpr, ComparisonExpr, TempNode, SetComprehension,
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
TypeAliasExpr, BackquoteExpr, EnumCallExpr,
ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE,
ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE
)
from mypy.literals import literal
from mypy import nodes
Expand Down Expand Up @@ -1796,14 +1796,29 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
context=expr)
return target_type

def visit_reveal_type_expr(self, expr: RevealTypeExpr) -> Type:
def visit_reveal_expr(self, expr: RevealExpr) -> Type:
"""Type check a reveal_type expression."""
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
if not self.chk.current_node_deferred:
self.msg.reveal_type(revealed_type, expr)
if not self.chk.in_checked_function():
self.msg.note("'reveal_type' always outputs 'Any' in unchecked functions", expr)
return revealed_type
if expr.kind == REVEAL_TYPE:
assert expr.expr is not None
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
if not self.chk.current_node_deferred:
self.msg.reveal_type(revealed_type, expr)
if not self.chk.in_checked_function():
self.msg.note("'reveal_type' always outputs 'Any' in unchecked functions",
expr)
return revealed_type
else:
# REVEAL_LOCALS
if not self.chk.current_node_deferred:
# the RevealExpr contains a local_nodes attribute,
# calculated at semantic analysis time. Use it to pull out the
# corresponding subset of variables in self.chk.type_map
names_to_types = {
var_node.name(): var_node.type for var_node in expr.local_nodes
} if expr.local_nodes is not None else {}

self.msg.reveal_locals(names_to_types, expr)
return NoneTyp()

def visit_type_application(self, tapp: TypeApplication) -> Type:
"""Type check a type application (expr[type, ...])."""
Expand Down
4 changes: 2 additions & 2 deletions mypy/literals.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Expression, ComparisonExpr, OpExpr, MemberExpr, UnaryExpr, StarExpr, IndexExpr, LITERAL_YES,
LITERAL_NO, NameExpr, LITERAL_TYPE, IntExpr, FloatExpr, ComplexExpr, StrExpr, BytesExpr,
UnicodeExpr, ListExpr, TupleExpr, SetExpr, DictExpr, CallExpr, SliceExpr, CastExpr,
ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealTypeExpr, SuperExpr,
ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealExpr, SuperExpr,
TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension,
GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr,
TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode,
Expand Down Expand Up @@ -175,7 +175,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> None:
def visit_yield_expr(self, e: YieldExpr) -> None:
return None

def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None:
def visit_reveal_expr(self, e: RevealExpr) -> None:
return None

def visit_super_expr(self, e: SuperExpr) -> None:
Expand Down
14 changes: 12 additions & 2 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
checker but we are moving away from this convention.
"""

from collections import OrderedDict
import re
import difflib

Expand All @@ -26,10 +27,9 @@
TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases,
ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2,
ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode,
CallExpr
CallExpr, Expression
)


# Constants that represent simple type checker error message, i.e. messages
# that do not have any parameters.

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

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

def unsupported_type_type(self, item: Type, context: Context) -> None:
self.fail('Unsupported type Type[{}]'.format(self.format(item)), context)

Expand Down
20 changes: 15 additions & 5 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ def get_column(self) -> int:
# XXX what?
UNBOUND_IMPORTED = 7 # type: int

# RevealExpr node kinds
REVEAL_TYPE = 0 # type: int
REVEAL_LOCALS = 1 # type: int

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


class RevealTypeExpr(Expression):
"""Reveal type expression reveal_type(expr)."""
class RevealExpr(Expression):
"""Reveal type expression reveal_type(expr) or reveal_locals() expression."""

expr = None # type: Expression
expr = None # type: Optional[Expression]
kind = 0 # type: int
local_nodes = None # type: Optional[List[Var]]

def __init__(self, expr: Expression) -> None:
def __init__(
self, kind: int,
expr: Optional[Expression] = None,
local_nodes: 'Optional[List[Var]]' = None) -> None:
super().__init__()
self.expr = expr
self.kind = kind
self.local_nodes = local_nodes

def accept(self, visitor: ExpressionVisitor[T]) -> T:
return visitor.visit_reveal_type_expr(self)
return visitor.visit_reveal_expr(self)


class SuperExpr(Expression):
Expand Down
47 changes: 42 additions & 5 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
RaiseStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt,
ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt, PassStmt,
GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr,
SliceExpr, CastExpr, RevealTypeExpr, TypeApplication, Context, SymbolTable,
SliceExpr, CastExpr, RevealExpr, TypeApplication, Context, SymbolTable,
SymbolTableNode, TVAR, ListComprehension, GeneratorExpr,
LambdaExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, NewTypeExpr,
StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr,
Expand All @@ -56,7 +56,7 @@
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, EnumCallExpr, ImportedName,
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ARG_OPT, nongen_builtins,
collections_type_aliases, get_member_expr_fullname,
collections_type_aliases, get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS
)
from mypy.literals import literal
from mypy.tvar_scope import TypeVarScope
Expand Down Expand Up @@ -2574,7 +2574,38 @@ def visit_call_expr(self, expr: CallExpr) -> None:
elif refers_to_fullname(expr.callee, 'builtins.reveal_type'):
if not self.check_fixed_args(expr, 1, 'reveal_type'):
return
expr.analyzed = RevealTypeExpr(expr.args[0])
expr.analyzed = RevealExpr(kind=REVEAL_TYPE, expr=expr.args[0])
expr.analyzed.line = expr.line
expr.analyzed.column = expr.column
expr.analyzed.accept(self)
elif refers_to_fullname(expr.callee, 'builtins.reveal_locals'):
# Store the local variable names into the RevealExpr for use in the
# type checking pass
local_nodes = [] # type: List[Var]
if self.is_module_scope():
# try to determine just the variable declarations in module scope
# self.globals.values() contains SymbolTableNode's
# Each SymbolTableNode has an attribute node that is nodes.Var
# look for variable nodes that marked as is_inferred
# Each symboltable node has a Var node as .node
local_nodes = cast(
List[Var],
[
n.node for name, n in self.globals.items()
if getattr(n.node, 'is_inferred', False)
]
)
elif self.is_class_scope():
# type = None # type: Optional[TypeInfo]
if self.type is not None:
local_nodes = cast(List[Var], [st.node for st in self.type.names.values()])
elif self.is_func_scope():
# locals = None # type: List[Optional[SymbolTable]]
if self.locals is not None:
symbol_table = self.locals[-1]
if symbol_table is not None:
local_nodes = cast(List[Var], [st.node for st in symbol_table.values()])
expr.analyzed = RevealExpr(kind=REVEAL_LOCALS, local_nodes=local_nodes)
expr.analyzed.line = expr.line
expr.analyzed.column = expr.column
expr.analyzed.accept(self)
Expand Down Expand Up @@ -2826,8 +2857,14 @@ def visit_cast_expr(self, expr: CastExpr) -> None:
expr.expr.accept(self)
expr.type = self.anal_type(expr.type)

def visit_reveal_type_expr(self, expr: RevealTypeExpr) -> None:
expr.expr.accept(self)
def visit_reveal_expr(self, expr: RevealExpr) -> None:
if expr.kind == REVEAL_TYPE:
if expr.expr is not None:
expr.expr.accept(self)
else:
# Reveal locals doesn't have an inner expression, there's no
# need to traverse inside it
pass

def visit_type_application(self, expr: TypeApplication) -> None:
expr.expr.accept(self)
Expand Down
3 changes: 3 additions & 0 deletions mypy/semanal_pass1.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -
# reveal_type is a mypy-only function that gives an error with
# the type of its arg.
('reveal_type', AnyType(TypeOfAny.special_form)),
# reveal_locals is a mypy-only function that gives an error with the types of
# locals
('reveal_locals', AnyType(TypeOfAny.special_form)),
] # type: List[Tuple[str, Type]]

# TODO(ddfisher): This guard is only needed because mypy defines
Expand Down
6 changes: 3 additions & 3 deletions mypy/semanal_pass3.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef,
Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr,
CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue,
TupleExpr, RevealTypeExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef,
TupleExpr, RevealExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef,
MDEF,
)
from mypy.types import (
Expand Down Expand Up @@ -278,8 +278,8 @@ def visit_cast_expr(self, e: CastExpr) -> None:
self.analyze(e.type, e)
super().visit_cast_expr(e)

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

def visit_type_application(self, e: TypeApplication) -> None:
for type in e.types:
Expand Down
6 changes: 3 additions & 3 deletions mypy/server/subexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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

def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None:
def visit_reveal_expr(self, e: RevealExpr) -> None:
self.add(e)
super().visit_reveal_type_expr(e)
super().visit_reveal_expr(e)

def visit_unary_expr(self, e: UnaryExpr) -> None:
self.add(e)
Expand Down
8 changes: 6 additions & 2 deletions mypy/strconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,12 @@ def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> str:
def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> str:
return self.dump([o.expr, o.type], o)

def visit_reveal_type_expr(self, o: 'mypy.nodes.RevealTypeExpr') -> str:
return self.dump([o.expr], o)
def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> str:
if o.kind == mypy.nodes.REVEAL_TYPE:
return self.dump([o.expr], o)
else:
# REVEAL_LOCALS
return self.dump([o.local_nodes], o)

def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> str:
return self.dump([o.op, o.expr], o)
Expand Down
13 changes: 9 additions & 4 deletions mypy/traverser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
Block, MypyFile, FuncItem, CallExpr, ClassDef, Decorator, FuncDef,
ExpressionStmt, AssignmentStmt, OperatorAssignmentStmt, WhileStmt,
ForStmt, ReturnStmt, AssertStmt, DelStmt, IfStmt, RaiseStmt,
TryStmt, WithStmt, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealTypeExpr,
TryStmt, WithStmt, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealExpr,
UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr, IndexExpr,
GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension,
ConditionalExpr, TypeApplication, ExecStmt, Import, ImportFrom,
LambdaExpr, ComparisonExpr, OverloadedFuncDef, YieldFromExpr,
YieldExpr, StarExpr, BackquoteExpr, AwaitExpr, PrintStmt, SuperExpr,
YieldExpr, StarExpr, BackquoteExpr, AwaitExpr, PrintStmt, SuperExpr, REVEAL_TYPE
)


Expand Down Expand Up @@ -181,8 +181,13 @@ def visit_slice_expr(self, o: SliceExpr) -> None:
def visit_cast_expr(self, o: CastExpr) -> None:
o.expr.accept(self)

def visit_reveal_type_expr(self, o: RevealTypeExpr) -> None:
o.expr.accept(self)
def visit_reveal_expr(self, o: RevealExpr) -> None:
if o.kind == REVEAL_TYPE:
assert o.expr is not None
o.expr.accept(self)
else:
# RevealLocalsExpr doesn't have an inner expression
pass

def visit_unary_expr(self, o: UnaryExpr) -> None:
o.expr.accept(self)
Expand Down
13 changes: 9 additions & 4 deletions mypy/treetransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
OperatorAssignmentStmt, ExpressionStmt, AssignmentStmt, ReturnStmt,
RaiseStmt, AssertStmt, DelStmt, BreakStmt, ContinueStmt,
PassStmt, GlobalDecl, WhileStmt, ForStmt, IfStmt, TryStmt, WithStmt,
CastExpr, RevealTypeExpr, TupleExpr, GeneratorExpr, ListComprehension, ListExpr,
CastExpr, RevealExpr, TupleExpr, GeneratorExpr, ListComprehension, ListExpr,
ConditionalExpr, DictExpr, SetExpr, NameExpr, IntExpr, StrExpr, BytesExpr,
UnicodeExpr, FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr,
SliceExpr, OpExpr, UnaryExpr, LambdaExpr, TypeApplication, PrintStmt,
Expand All @@ -20,7 +20,7 @@
YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension,
DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr,
YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr,
OverloadPart, EnumCallExpr,
OverloadPart, EnumCallExpr, REVEAL_TYPE
)
from mypy.types import Type, FunctionLike
from mypy.traverser import TraverserVisitor
Expand Down Expand Up @@ -377,8 +377,13 @@ def visit_cast_expr(self, node: CastExpr) -> CastExpr:
return CastExpr(self.expr(node.expr),
self.type(node.type))

def visit_reveal_type_expr(self, node: RevealTypeExpr) -> RevealTypeExpr:
return RevealTypeExpr(self.expr(node.expr))
def visit_reveal_expr(self, node: RevealExpr) -> RevealExpr:
if node.kind == REVEAL_TYPE:
assert node.expr is not None
return RevealExpr(kind=REVEAL_TYPE, expr=self.expr(node.expr))
else:
# Reveal locals expressions don't have any sub expressions
return node

def visit_super_expr(self, node: SuperExpr) -> SuperExpr:
call = self.expr(node.call)
Expand Down
Loading