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

736 lines, 438 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 self.current_method_name = stmt.name
250 self.oils_visit_dunder_exit(o, stmt, base_class_sym)
251 self.current_method_name = None
252 continue
253
254 self.current_method_name = stmt.name
255 self.oils_visit_method(o, stmt, base_class_sym)
256 self.current_method_name = None
257 continue
258
259 # if 0: is allowed
260 if isinstance(stmt, IfStmt):
261 self.accept(stmt)
262 continue
263
264 self.report_error(
265 o, 'Classes may only have method definitions, got %s' % stmt)
266
267 self.oils_visit_class_members(o, base_class_sym)
268
269 def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> None:
270 base_class_sym = None # single inheritance only
271 if len(o.base_type_exprs) > 1:
272 self.report_error(o, 'too many base types: %s' % o.base_type_exprs)
273 return
274
275 for b in o.base_type_exprs:
276 if isinstance(b, NameExpr):
277 if b.name != 'object' and b.name != 'Exception':
278 base_class_sym = SplitPyName(b.fullname)
279 elif isinstance(b, MemberExpr): # vm._Executor -> vm::_Executor
280 assert isinstance(b.expr, NameExpr), b
281 base_class_sym = SplitPyName(b.expr.fullname) + (b.name, )
282 else:
283 # shouldn't happen
284 raise AssertionError(b)
285
286 self.current_class_name = SplitPyName(o.fullname)
287 self.oils_visit_class_def(o, base_class_sym)
288 self.current_class_name = None
289
290 # Statements
291
292 def oils_visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt',
293 lval: Expression, rval: Expression) -> None:
294 self.accept(lval)
295 self.accept(rval)
296
297 def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> None:
298 # We never use this idiom: x = y = 42
299 assert len(o.lvalues) == 1, o.lvalues
300 lval = o.lvalues[0]
301
302 # Metadata we'll never use
303 if isinstance(lval, NameExpr):
304 if lval.name == '__all__':
305 return
306
307 # Special case for
308 if isinstance(o.rvalue, ListComprehension):
309 self.visit_assign_to_listcomp(o, lval)
310 return
311
312 # TODO: virtual pass might want this
313 # However it depends on TYPES. Right now the visitor doesn't depend on types
314 # if (isinstance(rval_type, Instance) and
315 # rval_type.type.fullname == 'typing.Iterator'):
316 # self._AssignToGenerator(o, lval, rval_type)
317 # return
318 # Key reason it needs to be in the virtual pass is that signatures
319 # must change to allow an out param.
320
321 self.oils_visit_assignment_stmt(o, lval, o.rvalue)
322
323 def visit_operator_assignment_stmt(
324 self, o: 'mypy.nodes.OperatorAssignmentStmt') -> None:
325 self.accept(o.lvalue)
326 self.accept(o.rvalue)
327
328 def visit_block(self, block: 'mypy.nodes.Block') -> None:
329 for stmt in block.body:
330 self.accept(stmt)
331
332 def oils_visit_expression_stmt(self,
333 o: 'mypy.nodes.ExpressionStmt') -> None:
334 self.accept(o.expr)
335
336 def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> None:
337 # Ignore all docstrings: module, class, and function body
338 if isinstance(o.expr, StrExpr):
339 return
340
341 # Either f() or obj.method()
342 assert isinstance(o.expr, (CallExpr, YieldExpr)), o.expr
343
344 self.oils_visit_expression_stmt(o)
345
346 def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> None:
347 self.accept(o.expr)
348 self.accept(o.body)
349
350 def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> None:
351 if o.expr:
352 self.accept(o.expr)
353
354 def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> None:
355 if util.ShouldVisitIfExpr(o):
356 for expr in o.expr:
357 self.accept(expr)
358
359 if util.ShouldVisitIfBody(o):
360 for body in o.body:
361 self.accept(body)
362
363 if util.ShouldVisitElseBody(o):
364 self.accept(o.else_body)
365
366 def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> None:
367 if o.expr:
368 self.accept(o.expr)
369
370 def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> None:
371 self.accept(o.body)
372 for handler in o.handlers:
373 self.accept(handler)
374
375 def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> None:
376 self.accept(o.expr)
377
378 # Expressions
379
380 def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> None:
381 raise AssertionError()
382
383 self.accept(o.left_expr)
384
385 for expr in o.indices:
386 self.accept(expr)
387
388 for expr in o.sequences:
389 self.accept(expr)
390
391 for l in o.condlists:
392 for expr in l:
393 self.accept(expr)
394
395 def visit_list_comprehension(self,
396 o: 'mypy.nodes.ListComprehension') -> None:
397 # old code
398 #self.accept(o.generator)
399 self.report_error(o,
400 'List comprehension must be assigned to a temp var')
401
402 def oils_visit_assign_to_listcomp(self, lval: NameExpr,
403 left_expr: Expression,
404 index_expr: Expression, seq: Expression,
405 cond: Expression) -> None:
406 self.accept(lval)
407 self.accept(left_expr)
408 self.accept(index_expr)
409 self.accept(seq)
410 if cond is not None:
411 self.accept(cond)
412
413 def visit_assign_to_listcomp(self, o: 'mypy.nodes.AssignmentStmt',
414 lval: NameExpr) -> None:
415 gen = o.rvalue.generator # GeneratorExpr
416
417 if (len(gen.indices) != 1 or len(gen.sequences) != 1 or
418 len(gen.condlists) != 1):
419 self.report_error(o, 'List comprehensions can only have one loop')
420
421 index_expr = gen.indices[0]
422 seq = gen.sequences[0]
423 condlist = gen.condlists[0]
424
425 if len(condlist) == 0:
426 cond: Optional[Expression] = None
427 elif len(condlist) == 1:
428 cond = condlist[0]
429 else:
430 self.report_error(
431 o, 'List comprehensions may have at most one condition')
432
433 self.oils_visit_assign_to_listcomp(lval, gen.left_expr, index_expr,
434 seq, cond)
435
436 def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> None:
437 self.accept(o.expr)
438
439 def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
440 self.accept(o.left)
441 self.accept(o.right)
442
443 def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> None:
444 for operand in o.operands:
445 self.accept(operand)
446
447 def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> None:
448 # e.g. -42 or 'not x'
449 self.accept(o.expr)
450
451 def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> None:
452 for item in o.items:
453 self.accept(item)
454
455 def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> None:
456 for k, v in o.items:
457 self.accept(k)
458 self.accept(v)
459
460 def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> None:
461 for item in o.items:
462 self.accept(item)
463
464 def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> None:
465 self.accept(o.base)
466 self.accept(o.index)
467
468 def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> None:
469 if o.begin_index:
470 self.accept(o.begin_index)
471
472 if o.end_index:
473 self.accept(o.end_index)
474
475 if o.stride:
476 self.accept(o.stride)
477
478 def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> None:
479 self.accept(o.cond)
480 self.accept(o.if_expr)
481 self.accept(o.else_expr)
482
483 def oils_visit_log_call(self, fmt: StrExpr,
484 args: List[Expression]) -> None:
485 self.accept(fmt)
486 for arg in args:
487 self.accept(arg)
488
489 def oils_visit_probe_call(self, o: 'mypy.nodes.CallExpr') -> None:
490 self.accept(o.callee)
491 for arg in o.args:
492 self.accept(arg)
493
494 def oils_visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> None:
495 self.accept(o.callee)
496 for arg in o.args:
497 self.accept(arg)
498
499 def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> None:
500 # Oils invariant: check that it'fs () or obj.method()
501 assert isinstance(o.callee, (NameExpr, MemberExpr)), o.callee
502
503 if isinstance(o.callee, NameExpr):
504 callee_name = o.callee.name
505
506 if callee_name == 'isinstance':
507 self.report_error(o, 'isinstance() not allowed')
508 return
509
510 if callee_name == 'log':
511 # Special printf-style varargs:
512 #
513 # log('foo %s', x)
514 # =>
515 # log(StrFormat('foo %s', x))
516
517 args = o.args
518 assert len(args) > 0, o
519 assert isinstance(args[0], StrExpr), args[0]
520 fmt = args[0]
521
522 self.oils_visit_log_call(fmt, args[1:])
523 return
524
525 if callee_name == 'probe':
526 # DTRACE_PROBE()
527 self.oils_visit_probe_call(o)
528 return
529
530 self.oils_visit_call_expr(o)
531
532 #
533 # Leaf Statements and Expressions that do nothing
534 #
535
536 def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> None:
537 pass
538
539 def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> None:
540 pass
541
542 def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> None:
543 pass
544
545 def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> None:
546 pass
547
548 def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> None:
549 pass
550
551 def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> None:
552 pass
553
554 def visit_import(self, o: 'mypy.nodes.Import') -> None:
555 pass
556
557 def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> None:
558 pass
559
560 def oils_visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> None:
561 pass
562
563 def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> None:
564 if o.name in NAME_CONFLICTS:
565 self.report_error(
566 o,
567 "The name %r conflicts with C macros on some platforms; choose a different name"
568 % o.name)
569 return
570
571 self.oils_visit_name_expr(o)
572
573 def oils_visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
574 self.accept(o.expr)
575
576 def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
577 if o.name in NAME_CONFLICTS:
578 self.report_error(
579 o,
580 "The name %r conflicts with C macros on some platforms; choose a different name"
581 % o.name)
582 return
583
584 self.oils_visit_member_expr(o)
585
586 #
587 # Not doing anything with these?
588 #
589
590 def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> None:
591 # I think casts are handle in AssignmentStmt
592 pass
593
594 def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> None:
595 # what is this?
596 pass
597
598 def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> None:
599 pass
600
601 def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> None:
602 pass
603
604 def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> None:
605 pass
606
607 def visit_var(self, o: 'mypy.nodes.Var') -> None:
608 # Is this a Python 3 class member?
609 pass
610
611 def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> None:
612 # no-op on purpose
613 pass
614
615 #
616 # Not part of the mycpp dialect
617 #
618
619 def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> None:
620 self.not_translated(o, 'lambda')
621
622 def visit_set_comprehension(self,
623 o: 'mypy.nodes.SetComprehension') -> None:
624 self.not_translated(o, 'set comp')
625
626 def visit_dictionary_comprehension(
627 self, o: 'mypy.nodes.DictionaryComprehension') -> None:
628 self.not_translated(o, 'dict comp')
629
630 def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> None:
631 self.not_translated(o, 'global')
632
633 def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> None:
634 self.not_translated(o, 'nonlocal')
635
636 def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> None:
637 self.report_error(o, 'exec not allowed')
638
639 # Error
640 def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> None:
641 self.report_error(
642 o,
643 'File should start with "from __future__ import print_function"')
644
645 # UNHANDLED
646
647 def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> None:
648 self.not_translated(o, 'ImportAll')
649
650 def visit_overloaded_func_def(self,
651 o: 'mypy.nodes.OverloadedFuncDef') -> None:
652 self.not_python2(o, 'overloaded func')
653
654 def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> None:
655 self.not_python2(o, 'bytes expr')
656
657 def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> None:
658 self.not_translated(o, 'unicode expr')
659
660 def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> None:
661 self.not_translated(o, 'complex expr')
662
663 def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> None:
664 self.not_translated(o, 'set expr')
665
666 def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> None:
667 # is this in .pyi files only?
668 self.not_translated(o, 'ellipsis')
669
670 def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> None:
671 self.not_python2(o, 'yield from')
672
673 def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> None:
674 # mycpp/examples/invalid_python.py doesn't hit this?
675 self.not_translated(o, 'star expr')
676
677 def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> None:
678 self.not_translated(o, 'super expr')
679
680 def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> None:
681 # I think this is a := b
682 self.not_translated(o, 'assign expr')
683
684 def visit_decorator(self, o: 'mypy.nodes.Decorator') -> None:
685 self.not_translated(o, 'decorator')
686
687 def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> None:
688 self.not_translated(o, 'backquote')
689
690 def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> None:
691 self.not_translated(o, 'namedtuple')
692
693 def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> None:
694 self.not_translated(o, 'enum')
695
696 def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> None:
697 self.not_translated(o, 'typed dict')
698
699 def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> None:
700 self.not_translated(o, 'newtype')
701
702 def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> None:
703 self.not_translated(o, 'promote')
704
705 def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> None:
706 self.not_translated(o, 'await')
707
708 def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> None:
709 self.not_translated(o, 'temp')
710
711
712class TypedVisitor(SimpleVisitor):
713 """Base class for visitors that need type info."""
714
715 def __init__(self, types: Dict[Expression, Type]) -> None:
716 SimpleVisitor.__init__(self)
717 self.types = types
718
719 def oils_visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
720 self.accept(o.left)
721 self.accept(o.right)
722
723 def oils_visit_format_expr(self, left: Expression,
724 right: Expression) -> None:
725 """ mystr % x mystr % (x, y) """
726 self.accept(left)
727 self.accept(right)
728
729 def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
730 if o.op == '%' and util.IsStr(self.types[o.left]):
731 # 'x = %r' % x
732 self.oils_visit_format_expr(o.left, o.right)
733 return
734
735 # Any other expression
736 self.oils_visit_op_expr(o)