OILS / mycpp / visitor.py View on Github | oils.pub

742 lines, 443 significant
1"""
2visitor.py - base class for all our tree traversals.
3
4It validates many nodes, i.e. presenting a subset of the MyPy AST to
5subclasses.
6"""
7import mypy
8from mypy.visitor import ExpressionVisitor, StatementVisitor
9from mypy.nodes import (Expression, Statement, ExpressionStmt, StrExpr,
10 CallExpr, YieldExpr, NameExpr, MemberExpr, Argument,
11 ClassDef, FuncDef, IfStmt, PassStmt, ListComprehension)
12from mypy.types import Type
13
14from mycpp.crash import catch_errors
15from mycpp import util
16from mycpp.util import SplitPyName, log
17
18from typing import (overload, Any, Union, Optional, List, Dict, Tuple, TextIO)
19
20NAME_CONFLICTS = ('stdin', 'stdout', 'stderr')
21
22
23class SimpleVisitor(ExpressionVisitor[None], StatementVisitor[None]):
24 """
25 A simple AST visitor that accepts every node in the AST. Derrived classes
26 can override the visit methods that are relevant to them.
27 """
28
29 def __init__(self) -> None:
30 self.module_path: Optional[str] = None
31
32 self.current_class_name: Optional[util.SymbolPath] = None
33 self.current_method_name: Optional[str] = None
34
35 # So we can report multiple at once
36 # module path, line number, message
37 self.errors_keep_going: List[Tuple[str, int, str]] = []
38
39 self.at_global_scope = True
40 self.indent = 0
41 self.f: Optional[TextIO] = None
42
43 def SetOutputFile(self, f: TextIO) -> None:
44 self.f = f
45
46 def log(self, msg: str, *args: Any) -> None:
47 """Log to STDERR"""
48 ind_str = self.indent * ' '
49 log(ind_str + msg, *args)
50
51 def write(self, msg: str, *args: Any) -> None:
52 if args:
53 msg = msg % args
54 assert self.f is not None
55 self.f.write(msg)
56
57 def write_ind(self, msg: str, *args: Any) -> None:
58 ind_str = self.indent * ' '
59 self.write(ind_str + msg, *args)
60
61 #
62 # COPIED from mypyc IRBuilder
63 #
64
65 @overload
66 def accept(self, node: Expression) -> None:
67 ...
68
69 @overload
70 def accept(self, node: Statement) -> None:
71 ...
72
73 def accept(self, node: Union[Statement, Expression]) -> None:
74 with catch_errors(self.module_path, node.line):
75 if isinstance(node, Expression):
76 node.accept(self)
77 else:
78 node.accept(self)
79
80 #
81 # Error API
82 #
83
84 def report_error(self, node: Union[Statement, Expression, Argument],
85 msg: str) -> None:
86 err = (self.module_path, node.line, msg)
87 self.errors_keep_going.append(err)
88
89 def not_translated(self, node: Union[Statement, Expression],
90 name: str) -> None:
91 self.report_error(node, '%s not translated' % name)
92
93 def not_python2(self, node: Union[Statement, Expression],
94 name: str) -> None:
95 self.report_error(node, "%s: shouldn't get here in Python 2" % name)
96
97 def oils_visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> None:
98 for node in o.defs:
99 self.accept(node)
100
101 def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> None:
102 if util.ShouldSkipPyFile(o):
103 return
104
105 self.module_path = o.path
106
107 self.oils_visit_mypy_file(o)
108
109 # Now show errors for each file
110 for path, line_num, msg in self.errors_keep_going:
111 self.log('%s:%s %s', path, line_num, msg)
112
113 def oils_visit_for_stmt(self, o: 'mypy.nodes.ForStmt',
114 func_name: Optional[str]) -> None:
115 self.accept(o.index) # index var expression
116 self.accept(o.expr)
117 self.accept(o.body)
118 if o.else_body:
119 raise AssertionError("can't translate for-else")
120
121 def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> None:
122
123 func_name = None # does the loop look like 'for x in func():' ?
124 if (isinstance(o.expr, CallExpr) and
125 isinstance(o.expr.callee, NameExpr)):
126 func_name = o.expr.callee.name
127
128 # In addition to func_name, can we also pass
129 # iterated_over
130 # enumerate() reversed() iteritems() is o.expr[0]
131 # otherwise it's o.expr
132 # And then you get the type, and if it's typing.Iterator, then the
133 # virtual pass can set self.yield_eager_for
134
135 self.oils_visit_for_stmt(o, func_name)
136
137 # TODO: validate and destructure the different kinds of loops
138 #
139 # xrange() - 1 index
140 # xrange negative
141 # enumerate() - 2 indices
142 # reversed() - container - 1 index
143 # iteritems() - dict, 2 indices
144 #
145 # - over list
146 # - over dict - list comprehensions would need this too
147 # - over iterator
148 #
149 # LHS
150 # - NameExpr
151 # - TupleExpr
152 #
153 # More destructuring:
154 #
155 # item_type - is it a Tuple?
156 # enumerate - o.inferred_item_type[1]
157 # otherwise (xrange, reversed, iteritems) - o.inferred_item_type
158 #
159 # elif isinstance(item_type, TupleType): # for x, y in pairs
160 # if over_dict:
161 # ...
162 # else # it's a List
163 # if isinstance(o.index, TupleExpr):
164 # ...
165 # self._write_tuple_unpacking(temp_name, o.index.items, item_type.items)
166 #
167 # We need to detect this
168 # And then also detect it for list comprehensions
169
170 # Two different tests:
171 # for loop test
172 # if isinstance(o.index, TupleExpr):
173
174 def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> None:
175 assert len(o.expr) == 1, o.expr
176 expr = o.expr[0]
177 assert isinstance(expr, CallExpr), expr
178 self.accept(expr)
179 self.accept(o.body)
180
181 def oils_visit_func_def(self, o: 'mypy.nodes.FuncDef') -> None:
182 """Only the functions we care about in Oils."""
183 for arg in o.arguments:
184 if arg.initializer:
185 self.accept(arg.initializer)
186
187 self.accept(o.body)
188
189 def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> None:
190 # This could be a free function or a method
191 # __init__ __exit__ and other methods call this, with self.current_class_name set
192
193 # If an assignment statement is not in a function or method, then it's at global scope
194
195 self.at_global_scope = False
196 self.oils_visit_func_def(o)
197 self.at_global_scope = True
198
199 #
200 # Classes
201 #
202
203 def oils_visit_constructor(self, o: ClassDef, stmt: FuncDef,
204 base_class_sym: util.SymbolPath) -> None:
205 self.accept(stmt)
206
207 def oils_visit_dunder_exit(self, o: ClassDef, stmt: FuncDef,
208 base_class_sym: util.SymbolPath) -> None:
209 self.accept(stmt)
210
211 def oils_visit_method(self, o: ClassDef, stmt: FuncDef,
212 base_class_sym: util.SymbolPath) -> None:
213 self.accept(stmt)
214
215 def oils_visit_class_members(self, o: ClassDef,
216 base_class_sym: util.SymbolPath) -> None:
217 """Hook for writing member vars."""
218 # Do nothing by default.
219 pass
220
221 def oils_visit_class_def(
222 self, o: 'mypy.nodes.ClassDef',
223 base_class_sym: Optional[util.SymbolPath]) -> None:
224
225 for stmt in o.defs.body:
226 # Skip class docstrings
227 if (isinstance(stmt, ExpressionStmt) and
228 isinstance(stmt.expr, StrExpr)):
229 continue
230
231 # Skip empty classes
232 if isinstance(stmt, PassStmt):
233 continue
234
235 if isinstance(stmt, FuncDef):
236 method_name = stmt.name
237
238 # Don't translate
239 if method_name in ('__enter__', '__repr__'):
240 continue
241
242 if method_name == '__init__': # Don't translate
243 self.current_method_name = stmt.name
244 self.oils_visit_constructor(o, stmt, base_class_sym)
245 self.current_method_name = None
246 continue
247
248 if method_name == '__exit__': # Don't translate
249 if not o.name.startswith('ctx_'):
250 self.report_error(
251 o,
252 'Class with __exit__ should be named ctx_Foo: %s' %
253 (self.current_class_name, ))
254
255 self.current_method_name = stmt.name
256 self.oils_visit_dunder_exit(o, stmt, base_class_sym)
257 self.current_method_name = None
258 continue
259
260 self.current_method_name = stmt.name
261 self.oils_visit_method(o, stmt, base_class_sym)
262 self.current_method_name = None
263 continue
264
265 # if 0: is allowed
266 if isinstance(stmt, IfStmt):
267 self.accept(stmt)
268 continue
269
270 self.report_error(
271 o, 'Classes may only have method definitions, got %s' % stmt)
272
273 self.oils_visit_class_members(o, base_class_sym)
274
275 def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> None:
276 base_class_sym = None # single inheritance only
277 if len(o.base_type_exprs) > 1:
278 self.report_error(o, 'too many base types: %s' % o.base_type_exprs)
279 return
280
281 for b in o.base_type_exprs:
282 if isinstance(b, NameExpr):
283 if b.name != 'object' and b.name != 'Exception':
284 base_class_sym = SplitPyName(b.fullname)
285 elif isinstance(b, MemberExpr): # vm._Executor -> vm::_Executor
286 assert isinstance(b.expr, NameExpr), b
287 base_class_sym = SplitPyName(b.expr.fullname) + (b.name, )
288 else:
289 # shouldn't happen
290 raise AssertionError(b)
291
292 self.current_class_name = SplitPyName(o.fullname)
293 self.oils_visit_class_def(o, base_class_sym)
294 self.current_class_name = None
295
296 # Statements
297
298 def oils_visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt',
299 lval: Expression, rval: Expression) -> None:
300 self.accept(lval)
301 self.accept(rval)
302
303 def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> None:
304 # We never use this idiom: x = y = 42
305 assert len(o.lvalues) == 1, o.lvalues
306 lval = o.lvalues[0]
307
308 # Metadata we'll never use
309 if isinstance(lval, NameExpr):
310 if lval.name == '__all__':
311 return
312
313 # Special case for
314 if isinstance(o.rvalue, ListComprehension):
315 self.visit_assign_to_listcomp(o, lval)
316 return
317
318 # TODO: virtual pass might want this
319 # However it depends on TYPES. Right now the visitor doesn't depend on types
320 # if (isinstance(rval_type, Instance) and
321 # rval_type.type.fullname == 'typing.Iterator'):
322 # self._AssignToGenerator(o, lval, rval_type)
323 # return
324 # Key reason it needs to be in the virtual pass is that signatures
325 # must change to allow an out param.
326
327 self.oils_visit_assignment_stmt(o, lval, o.rvalue)
328
329 def visit_operator_assignment_stmt(
330 self, o: 'mypy.nodes.OperatorAssignmentStmt') -> None:
331 self.accept(o.lvalue)
332 self.accept(o.rvalue)
333
334 def visit_block(self, block: 'mypy.nodes.Block') -> None:
335 for stmt in block.body:
336 self.accept(stmt)
337
338 def oils_visit_expression_stmt(self,
339 o: 'mypy.nodes.ExpressionStmt') -> None:
340 self.accept(o.expr)
341
342 def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> None:
343 # Ignore all docstrings: module, class, and function body
344 if isinstance(o.expr, StrExpr):
345 return
346
347 # Either f() or obj.method()
348 assert isinstance(o.expr, (CallExpr, YieldExpr)), o.expr
349
350 self.oils_visit_expression_stmt(o)
351
352 def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> None:
353 self.accept(o.expr)
354 self.accept(o.body)
355
356 def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> None:
357 if o.expr:
358 self.accept(o.expr)
359
360 def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> None:
361 if util.ShouldVisitIfExpr(o):
362 for expr in o.expr:
363 self.accept(expr)
364
365 if util.ShouldVisitIfBody(o):
366 for body in o.body:
367 self.accept(body)
368
369 if util.ShouldVisitElseBody(o):
370 self.accept(o.else_body)
371
372 def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> None:
373 if o.expr:
374 self.accept(o.expr)
375
376 def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> None:
377 self.accept(o.body)
378 for handler in o.handlers:
379 self.accept(handler)
380
381 def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> None:
382 self.accept(o.expr)
383
384 # Expressions
385
386 def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> None:
387 raise AssertionError()
388
389 self.accept(o.left_expr)
390
391 for expr in o.indices:
392 self.accept(expr)
393
394 for expr in o.sequences:
395 self.accept(expr)
396
397 for l in o.condlists:
398 for expr in l:
399 self.accept(expr)
400
401 def visit_list_comprehension(self,
402 o: 'mypy.nodes.ListComprehension') -> None:
403 # old code
404 #self.accept(o.generator)
405 self.report_error(o,
406 'List comprehension must be assigned to a temp var')
407
408 def oils_visit_assign_to_listcomp(self, lval: NameExpr,
409 left_expr: Expression,
410 index_expr: Expression, seq: Expression,
411 cond: Expression) -> None:
412 self.accept(lval)
413 self.accept(left_expr)
414 self.accept(index_expr)
415 self.accept(seq)
416 if cond is not None:
417 self.accept(cond)
418
419 def visit_assign_to_listcomp(self, o: 'mypy.nodes.AssignmentStmt',
420 lval: NameExpr) -> None:
421 gen = o.rvalue.generator # GeneratorExpr
422
423 if (len(gen.indices) != 1 or len(gen.sequences) != 1 or
424 len(gen.condlists) != 1):
425 self.report_error(o, 'List comprehensions can only have one loop')
426
427 index_expr = gen.indices[0]
428 seq = gen.sequences[0]
429 condlist = gen.condlists[0]
430
431 if len(condlist) == 0:
432 cond: Optional[Expression] = None
433 elif len(condlist) == 1:
434 cond = condlist[0]
435 else:
436 self.report_error(
437 o, 'List comprehensions may have at most one condition')
438
439 self.oils_visit_assign_to_listcomp(lval, gen.left_expr, index_expr,
440 seq, cond)
441
442 def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> None:
443 self.accept(o.expr)
444
445 def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
446 self.accept(o.left)
447 self.accept(o.right)
448
449 def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> None:
450 for operand in o.operands:
451 self.accept(operand)
452
453 def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> None:
454 # e.g. -42 or 'not x'
455 self.accept(o.expr)
456
457 def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> None:
458 for item in o.items:
459 self.accept(item)
460
461 def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> None:
462 for k, v in o.items:
463 self.accept(k)
464 self.accept(v)
465
466 def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> None:
467 for item in o.items:
468 self.accept(item)
469
470 def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> None:
471 self.accept(o.base)
472 self.accept(o.index)
473
474 def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> None:
475 if o.begin_index:
476 self.accept(o.begin_index)
477
478 if o.end_index:
479 self.accept(o.end_index)
480
481 if o.stride:
482 self.accept(o.stride)
483
484 def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> None:
485 self.accept(o.cond)
486 self.accept(o.if_expr)
487 self.accept(o.else_expr)
488
489 def oils_visit_log_call(self, fmt: StrExpr,
490 args: List[Expression]) -> None:
491 self.accept(fmt)
492 for arg in args:
493 self.accept(arg)
494
495 def oils_visit_probe_call(self, o: 'mypy.nodes.CallExpr') -> None:
496 self.accept(o.callee)
497 for arg in o.args:
498 self.accept(arg)
499
500 def oils_visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> None:
501 self.accept(o.callee)
502 for arg in o.args:
503 self.accept(arg)
504
505 def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> None:
506 # Oils invariant: check that it'fs () or obj.method()
507 assert isinstance(o.callee, (NameExpr, MemberExpr)), o.callee
508
509 if isinstance(o.callee, NameExpr):
510 callee_name = o.callee.name
511
512 if callee_name == 'isinstance':
513 self.report_error(o, 'isinstance() not allowed')
514 return
515
516 if callee_name == 'log':
517 # Special printf-style varargs:
518 #
519 # log('foo %s', x)
520 # =>
521 # log(StrFormat('foo %s', x))
522
523 args = o.args
524 assert len(args) > 0, o
525 assert isinstance(args[0], StrExpr), args[0]
526 fmt = args[0]
527
528 self.oils_visit_log_call(fmt, args[1:])
529 return
530
531 if callee_name == 'probe':
532 # DTRACE_PROBE()
533 self.oils_visit_probe_call(o)
534 return
535
536 self.oils_visit_call_expr(o)
537
538 #
539 # Leaf Statements and Expressions that do nothing
540 #
541
542 def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> None:
543 pass
544
545 def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> None:
546 pass
547
548 def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> None:
549 pass
550
551 def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> None:
552 pass
553
554 def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> None:
555 pass
556
557 def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> None:
558 pass
559
560 def visit_import(self, o: 'mypy.nodes.Import') -> None:
561 pass
562
563 def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> None:
564 pass
565
566 def oils_visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> None:
567 pass
568
569 def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> None:
570 if o.name in NAME_CONFLICTS:
571 self.report_error(
572 o,
573 "The name %r conflicts with C macros on some platforms; choose a different name"
574 % o.name)
575 return
576
577 self.oils_visit_name_expr(o)
578
579 def oils_visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
580 self.accept(o.expr)
581
582 def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
583 if o.name in NAME_CONFLICTS:
584 self.report_error(
585 o,
586 "The name %r conflicts with C macros on some platforms; choose a different name"
587 % o.name)
588 return
589
590 self.oils_visit_member_expr(o)
591
592 #
593 # Not doing anything with these?
594 #
595
596 def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> None:
597 # I think casts are handle in AssignmentStmt
598 pass
599
600 def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> None:
601 # what is this?
602 pass
603
604 def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> None:
605 pass
606
607 def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> None:
608 pass
609
610 def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> None:
611 pass
612
613 def visit_var(self, o: 'mypy.nodes.Var') -> None:
614 # Is this a Python 3 class member?
615 pass
616
617 def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> None:
618 # no-op on purpose
619 pass
620
621 #
622 # Not part of the mycpp dialect
623 #
624
625 def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> None:
626 self.not_translated(o, 'lambda')
627
628 def visit_set_comprehension(self,
629 o: 'mypy.nodes.SetComprehension') -> None:
630 self.not_translated(o, 'set comp')
631
632 def visit_dictionary_comprehension(
633 self, o: 'mypy.nodes.DictionaryComprehension') -> None:
634 self.not_translated(o, 'dict comp')
635
636 def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> None:
637 self.not_translated(o, 'global')
638
639 def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> None:
640 self.not_translated(o, 'nonlocal')
641
642 def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> None:
643 self.report_error(o, 'exec not allowed')
644
645 # Error
646 def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> None:
647 self.report_error(
648 o,
649 'File should start with "from __future__ import print_function"')
650
651 # UNHANDLED
652
653 def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> None:
654 self.not_translated(o, 'ImportAll')
655
656 def visit_overloaded_func_def(self,
657 o: 'mypy.nodes.OverloadedFuncDef') -> None:
658 self.not_python2(o, 'overloaded func')
659
660 def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> None:
661 self.not_python2(o, 'bytes expr')
662
663 def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> None:
664 self.not_translated(o, 'unicode expr')
665
666 def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> None:
667 self.not_translated(o, 'complex expr')
668
669 def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> None:
670 self.not_translated(o, 'set expr')
671
672 def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> None:
673 # is this in .pyi files only?
674 self.not_translated(o, 'ellipsis')
675
676 def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> None:
677 self.not_python2(o, 'yield from')
678
679 def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> None:
680 # mycpp/examples/invalid_python.py doesn't hit this?
681 self.not_translated(o, 'star expr')
682
683 def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> None:
684 self.not_translated(o, 'super expr')
685
686 def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> None:
687 # I think this is a := b
688 self.not_translated(o, 'assign expr')
689
690 def visit_decorator(self, o: 'mypy.nodes.Decorator') -> None:
691 self.not_translated(o, 'decorator')
692
693 def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> None:
694 self.not_translated(o, 'backquote')
695
696 def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> None:
697 self.not_translated(o, 'namedtuple')
698
699 def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> None:
700 self.not_translated(o, 'enum')
701
702 def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> None:
703 self.not_translated(o, 'typed dict')
704
705 def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> None:
706 self.not_translated(o, 'newtype')
707
708 def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> None:
709 self.not_translated(o, 'promote')
710
711 def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> None:
712 self.not_translated(o, 'await')
713
714 def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> None:
715 self.not_translated(o, 'temp')
716
717
718class TypedVisitor(SimpleVisitor):
719 """Base class for visitors that need type info."""
720
721 def __init__(self, types: Dict[Expression, Type]) -> None:
722 SimpleVisitor.__init__(self)
723 self.types = types
724
725 def oils_visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
726 self.accept(o.left)
727 self.accept(o.right)
728
729 def oils_visit_format_expr(self, left: Expression,
730 right: Expression) -> None:
731 """ mystr % x mystr % (x, y) """
732 self.accept(left)
733 self.accept(right)
734
735 def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
736 if o.op == '%' and util.IsStr(self.types[o.left]):
737 # 'x = %r' % x
738 self.oils_visit_format_expr(o.left, o.right)
739 return
740
741 # Any other expression
742 self.oils_visit_op_expr(o)