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

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