OILS / mycpp / cppgen_pass.py View on Github | oilshell.org

2823 lines, 1770 significant
1"""
2cppgen_pass.py - AST pass that prints C++ code
3"""
4import itertools
5import json # for "C escaping"
6
7from typing import Union, Optional, Dict
8
9import mypy
10from mycpp import visitor
11from mypy.types import (Type, AnyType, NoneTyp, TupleType, Instance,
12 Overloaded, CallableType, UnionType, UninhabitedType,
13 PartialType, TypeAliasType)
14from mypy.nodes import (Expression, Statement, NameExpr, IndexExpr, MemberExpr,
15 TupleExpr, ExpressionStmt, IfStmt, StrExpr, SliceExpr,
16 FuncDef, UnaryExpr, OpExpr, CallExpr, ListExpr,
17 DictExpr, ClassDef, ForStmt, AssignmentStmt)
18
19from mycpp import format_strings
20from mycpp import pass_state
21from mycpp import util
22from mycpp.util import log, SymbolToString, SplitPyName
23
24from typing import Tuple, List, Any, TYPE_CHECKING
25if TYPE_CHECKING:
26 from mycpp import const_pass
27 from mycpp import conversion_pass
28
29
30def _IsContextManager(class_name: util.SymbolPath) -> bool:
31 return class_name[-1].startswith('ctx_')
32
33
34def _GetCTypeForCast(type_expr: Expression) -> str:
35 """ MyPy cast() """
36
37 if isinstance(type_expr, MemberExpr):
38 left = type_expr.expr
39 assert isinstance(left, NameExpr), left # assume it's module.Type
40 subtype_name = '%s::%s' % (left.name, type_expr.name)
41 elif isinstance(type_expr, IndexExpr):
42 # List[word_t] would be a problem.
43 # But worked around it in osh/word_parse.py
44 #subtype_name = 'List<word_t>'
45 raise AssertionError()
46 elif isinstance(type_expr, StrExpr):
47 parts = type_expr.value.split('.')
48 subtype_name = '::'.join(parts)
49 elif isinstance(type_expr, NameExpr):
50 subtype_name = type_expr.name
51 else:
52 raise AssertionError()
53
54 # Hack for now
55 if subtype_name != 'int' and subtype_name != 'mops::BigInt':
56 subtype_name += '*'
57 return subtype_name
58
59
60def _GetCastKind(module_path: str, cast_to_type: str) -> str:
61 """Translate MyPy cast to C++ cast.
62
63 Prefer static_cast, but sometimes we need reinterpret_cast.
64 """
65 cast_kind = 'static_cast'
66
67 # Hack for Id.Expr_CastedDummy in expr_to_ast.py
68 if 'expr_to_ast.py' in module_path:
69 for name in (
70 'ShArrayLiteral',
71 'CommandSub',
72 'BracedVarSub',
73 'DoubleQuoted',
74 'SingleQuoted',
75 # Another kind of hack, not because of CastDummy
76 'y_lhs_t',
77 ):
78 if name in cast_to_type:
79 cast_kind = 'reinterpret_cast'
80 break
81
82 # The other side of Id.Expr_CastedDummy
83 if 'expr_parse.py' in module_path:
84 for name in ('Token', ):
85 if name in cast_to_type:
86 cast_kind = 'reinterpret_cast'
87 break
88
89 if 'process.py' in module_path and 'mylib::Writer' in cast_to_type:
90 cast_kind = 'reinterpret_cast'
91
92 return cast_kind
93
94
95def _ContainsFunc(t: Type) -> Optional[str]:
96 """ x in y """
97 contains_func = None
98
99 if isinstance(t, Instance):
100 type_name = t.type.fullname
101
102 if type_name == 'builtins.list':
103 contains_func = 'list_contains'
104
105 elif type_name == 'builtins.str':
106 contains_func = 'str_contains'
107
108 elif type_name == 'builtins.dict':
109 contains_func = 'dict_contains'
110
111 elif isinstance(t, UnionType):
112 # Special case for Optional[T] == Union[T, None]
113 if len(t.items) != 2:
114 raise NotImplementedError('Expected Optional, got %s' % t)
115
116 if not isinstance(t.items[1], NoneTyp):
117 raise NotImplementedError('Expected Optional, got %s' % t)
118
119 contains_func = _ContainsFunc(t.items[0])
120
121 return contains_func # None checked later
122
123
124def _EqualsFunc(left_type: Type) -> Optional[str]:
125 if util.IsStr(left_type):
126 return 'str_equals'
127
128 if (isinstance(left_type, UnionType) and len(left_type.items) == 2 and
129 util.IsStr(left_type.items[0]) and
130 isinstance(left_type.items[1], NoneTyp)):
131 return 'maybe_str_equals'
132
133 return None
134
135
136_EXPLICIT = ('builtins.str', 'builtins.list', 'builtins.dict')
137
138
139def _CheckCondition(node: Expression, types: Dict[Expression, Type]) -> bool:
140 """
141 Ban
142 if (mystr)
143 if (mylist)
144 if (mydict)
145
146 They mean non-empty in Python.
147 """
148 #log('NODE %s', node)
149
150 if isinstance(node, UnaryExpr) and node.op == 'not':
151 return _CheckCondition(node.expr, types)
152
153 if isinstance(node, OpExpr):
154 #log('OpExpr node %s %s', node, dir(node))
155
156 # if x > 0 and not mylist, etc.
157 return (_CheckCondition(node.left, types) and
158 _CheckCondition(node.right, types))
159
160 t = types[node]
161
162 if isinstance(t, Instance):
163 type_name = t.type.fullname
164 if type_name in _EXPLICIT:
165 return False
166
167 elif isinstance(t, UnionType):
168 if len(t.items) == 2 and isinstance(t.items[1], NoneTyp):
169 t2 = t.items[0]
170 assert isinstance(t2, Instance), t2
171 if t2.type.fullname in _EXPLICIT:
172 return False
173
174 return True
175
176
177def CTypeIsManaged(c_type: str) -> bool:
178 """For rooting and field masks."""
179 assert c_type != 'void'
180
181 if util.SMALL_STR:
182 if c_type == 'Str':
183 return True
184
185 # int, double, bool, scope_t enums, etc. are not managed
186 return c_type.endswith('*')
187
188
189def GetCType(t: Type) -> str:
190 """Recursively translate MyPy type to C++ type."""
191 is_pointer = False
192
193 if isinstance(t, UninhabitedType):
194 # UninhabitedType is used by def e_usage() -> NoReturn
195 # TODO: we could add [[noreturn]] here!
196 c_type = 'void'
197
198 elif isinstance(t, PartialType):
199 # I removed the last instance of this! It was dead code in comp_ui.py.
200 raise AssertionError()
201 #c_type = 'void'
202 #is_pointer = True
203
204 elif isinstance(t,
205 NoneTyp): # e.g. a function that doesn't return anything
206 return 'void'
207
208 elif isinstance(t, AnyType):
209 # 'any' in ASDL becomes void*
210 # It's useful for value::BuiltinFunc(void* f) which is a vm::_Callable*
211 c_type = 'void'
212 is_pointer = True
213
214 elif isinstance(t, CallableType):
215 # Function types are expanded
216 # Callable[[Parser, Token, int], arith_expr_t]
217 # -> arith_expr_t* (*f)(Parser*, Token*, int) nud;
218
219 ret_type = GetCType(t.ret_type)
220 arg_types = [GetCType(typ) for typ in t.arg_types]
221 c_type = '%s (*f)(%s)' % (ret_type, ', '.join(arg_types))
222
223 elif isinstance(t, TypeAliasType):
224 if 0:
225 log('***')
226 log('%s', t)
227 log('%s', dir(t))
228 log('%s', t.alias)
229 log('%s', dir(t.alias))
230 log('%s', t.alias.target)
231 log('***')
232 return GetCType(t.alias.target)
233
234 elif isinstance(t, Instance):
235 type_name = t.type.fullname
236 #log('** TYPE NAME %s', type_name)
237
238 if type_name == 'builtins.int':
239 c_type = 'int'
240
241 elif type_name == 'builtins.float':
242 c_type = 'double'
243
244 elif type_name == 'builtins.bool':
245 c_type = 'bool'
246
247 elif type_name == 'builtins.str':
248 if util.SMALL_STR:
249 c_type = 'Str'
250 is_pointer = False
251 else:
252 c_type = 'BigStr'
253 is_pointer = True
254
255 elif 'BigInt' in type_name:
256 # also spelled mycpp.mylib.BigInt
257
258 c_type = 'mops::BigInt'
259 # Not a pointer!
260
261 elif type_name == 'typing.IO':
262 c_type = 'mylib::File'
263 is_pointer = True
264
265 # Parameterized types: List, Dict, Iterator
266 elif type_name == 'builtins.list':
267 assert len(t.args) == 1, t.args
268 type_param = t.args[0]
269 inner_c_type = GetCType(type_param)
270 c_type = 'List<%s>' % inner_c_type
271 is_pointer = True
272
273 elif type_name == 'builtins.dict':
274 params = []
275 for type_param in t.args:
276 params.append(GetCType(type_param))
277 c_type = 'Dict<%s>' % ', '.join(params)
278 is_pointer = True
279
280 elif type_name == 'typing.Iterator':
281 assert len(t.args) == 1, t.args
282 type_param = t.args[0]
283 inner_c_type = GetCType(type_param)
284 c_type = 'ListIter<%s>' % inner_c_type
285
286 else:
287 parts = t.type.fullname.split('.')
288 c_type = '%s::%s' % (parts[-2], parts[-1])
289
290 # note: fullname => 'parse.Lexer'; name => 'Lexer'
291 base_class_names = [b.type.fullname for b in t.type.bases]
292
293 # Check base class for pybase.SimpleObj so we can output
294 # expr_asdl::tok_t instead of expr_asdl::tok_t*. That is a enum, while
295 # expr_t is a "regular base class".
296 # NOTE: Could we avoid the typedef? If it's SimpleObj, just generate
297 # tok_e instead?
298
299 if 'asdl.pybase.SimpleObj' not in base_class_names:
300 is_pointer = True
301
302 elif isinstance(t, TupleType):
303 inner_c_types = [GetCType(inner) for inner in t.items]
304 c_type = 'Tuple%d<%s>' % (len(t.items), ', '.join(inner_c_types))
305 is_pointer = True
306
307 elif isinstance(t, UnionType): # Optional[T]
308
309 num_items = len(t.items)
310
311 if num_items == 3:
312 # Special case for Optional[IOError_OSError] ==
313 # Union[IOError, # OSError, None]
314 t0 = t.items[0]
315 t1 = t.items[1]
316 t2 = t.items[2]
317
318 assert isinstance(t0, Instance), t0
319 assert isinstance(t1, Instance), t1
320 t0_name = t0.type.fullname
321 t1_name = t1.type.fullname
322
323 if t0_name != 'builtins.IOError':
324 raise NotImplementedError(
325 'Expected Union[IOError, OSError, None]: t0 = %s' %
326 t0_name)
327
328 if t1_name != 'builtins.OSError':
329 raise NotImplementedError(
330 'Expected Union[IOError, OSError, None]: t1 = %s' %
331 t1_name)
332
333 if not isinstance(t2, NoneTyp):
334 raise NotImplementedError(
335 'Expected Union[IOError, OSError, None]')
336
337 c_type = 'IOError_OSError'
338 is_pointer = True
339
340 elif num_items == 2:
341 # Optional[T]
342
343 t0 = t.items[0]
344 t1 = t.items[1]
345
346 c_type = None
347 if isinstance(t1, NoneTyp):
348 c_type = GetCType(t.items[0])
349 else:
350 assert isinstance(t0, Instance), t0
351 assert isinstance(t1, Instance), t1
352
353 # Detect type alias defined in core/error.py
354 # IOError_OSError = Union[IOError, OSError]
355 t0_name = t0.type.fullname
356 t1_name = t1.type.fullname
357 if (t0_name == 'builtins.IOError' and
358 t1_name == 'builtins.OSError'):
359 c_type = 'IOError_OSError'
360 is_pointer = True
361
362 if c_type is None:
363 raise NotImplementedError('Unexpected Union type %s' % t)
364
365 else:
366 raise NotImplementedError(
367 'Expected 2 or 3 items in Union, got %s' % num_items)
368
369 else:
370 raise NotImplementedError('MyPy type: %s %s' % (type(t), t))
371
372 if is_pointer:
373 c_type += '*'
374
375 return c_type
376
377
378def GetCReturnType(t: Type) -> Tuple[str, bool, Optional[str]]:
379 """
380 Returns a C string, whether the tuple-by-value optimization was applied,
381 and the C type of an extra output param if the function is a generator.
382 """
383
384 c_ret_type = GetCType(t)
385
386 # Optimization: Return tuples BY VALUE
387 if isinstance(t, TupleType):
388 assert c_ret_type.endswith('*')
389 return c_ret_type[:-1], True, None
390 elif c_ret_type.startswith('ListIter<'):
391 assert len(t.args) == 1, t.args
392 inner_c_type = GetCType(t.args[0])
393 return 'void', False, 'List<%s>*' % inner_c_type
394 else:
395 return c_ret_type, False, None
396
397
398def PythonStringLiteral(s: str) -> str:
399 """
400 Returns a properly quoted string.
401 """
402 # MyPy does bad escaping. Decode and push through json to get something
403 # workable in C++.
404 return json.dumps(format_strings.DecodeMyPyString(s))
405
406
407def _GetNoReturn(func_name: str) -> str:
408 # Avoid C++ warnings by prepending [[noreturn]]
409 if func_name in ('e_die', 'e_die_status', 'e_strict', 'e_usage', 'p_die'):
410 return '[[noreturn]] '
411 else:
412 return ''
413
414
415# name, type, is_param
416LocalVar = Tuple[str, Type, bool]
417
418# lval_type, c_type, is_managed
419MemberVar = Tuple[Type, str, bool]
420
421AllMemberVars = Dict[ClassDef, Dict[str, MemberVar]]
422
423AllLocalVars = Dict[FuncDef, List[Tuple[str, Type]]]
424
425
426class _Shared(visitor.TypedVisitor):
427
428 def __init__(
429 self,
430 types: Dict[Expression, Type],
431 global_strings: 'const_pass.GlobalStrings',
432 yield_out_params: Dict[FuncDef, Tuple[str, str]], # input
433 # all_member_vars:
434 # - Decl for declaring members in class { }
435 # - Impl for rooting context managers
436 all_member_vars: Optional[AllMemberVars] = None,
437 ) -> None:
438 visitor.TypedVisitor.__init__(self, types)
439 self.global_strings = global_strings
440 self.yield_out_params = yield_out_params
441 self.all_member_vars = all_member_vars # for class def, and rooting
442
443 # Primitives shared for default values
444
445 def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> None:
446 self.write(str(o.value))
447
448 def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> None:
449 # e.g. for arg.t > 0.0
450 self.write(str(o.value))
451
452 def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> None:
453 self.write(self.global_strings.GetVarName(o))
454
455 def oils_visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> None:
456 if o.name == 'None':
457 self.write('nullptr')
458 return
459 if o.name == 'True':
460 self.write('true')
461 return
462 if o.name == 'False':
463 self.write('false')
464 return
465 if o.name == 'self':
466 self.write('this')
467 return
468
469 self.write(o.name)
470
471 def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> None:
472 # e.g. a[-1] or 'not x'
473 if o.op == 'not':
474 op_str = '!'
475 else:
476 op_str = o.op
477 self.write(op_str)
478 self.accept(o.expr)
479
480 def _NamespaceComment(self) -> str:
481 # abstract method
482 raise NotImplementedError()
483
484 def oils_visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> None:
485 mod_parts = o.fullname.split('.')
486 comment = self._NamespaceComment()
487
488 self.write_ind('namespace %s { // %s\n', mod_parts[-1], comment)
489 self.write('\n')
490
491 #self.log('defs %s', o.defs)
492 for node in o.defs:
493 self.accept(node)
494
495 self.write('\n')
496 self.write_ind('} // %s namespace %s\n', comment, mod_parts[-1])
497 self.write('\n')
498
499 def _WriteFuncParams(self,
500 func_def: FuncDef,
501 write_defaults: bool = False) -> None:
502 """Write params for function/method signatures."""
503 arg_types = func_def.type.arg_types
504 arguments = func_def.arguments
505
506 is_first = True # EXCLUDING 'self'
507 for arg_type, arg in zip(arg_types, arguments):
508 if not is_first:
509 self.write(', ')
510
511 c_type = GetCType(arg_type)
512
513 arg_name = arg.variable.name
514
515 # C++ has implicit 'this'
516 if arg_name == 'self':
517 continue
518
519 # int foo
520 self.write('%s %s', c_type, arg_name)
521
522 if write_defaults and arg.initializer: # int foo = 42
523 self.write(' = ')
524 self.accept(arg.initializer)
525
526 is_first = False
527
528 if 0:
529 self.log('Argument %s', arg.variable)
530 self.log(' type_annotation %s', arg.type_annotation)
531 # I think these are for default values
532 self.log(' initializer %s', arg.initializer)
533 self.log(' kind %s', arg.kind)
534
535 # Is the function we're writing params for an iterator?
536 if func_def in self.yield_out_params:
537 self.write(', ')
538
539 arg_name, c_type = self.yield_out_params[func_def]
540 self.write('%s %s', c_type, arg_name)
541
542
543class Decl(_Shared):
544
545 def __init__(
546 self,
547 types: Dict[Expression, Type],
548 global_strings: 'const_pass.GlobalStrings',
549 yield_out_params: Dict[FuncDef, Tuple[str, str]], # input
550 virtual: pass_state.Virtual = None,
551 all_member_vars: Optional[AllMemberVars] = None,
552 ) -> None:
553 _Shared.__init__(
554 self,
555 types,
556 global_strings,
557 yield_out_params,
558 all_member_vars=all_member_vars,
559 )
560 self.virtual = virtual
561
562 def _NamespaceComment(self) -> str:
563 # abstract method
564 return 'declare'
565
566 def oils_visit_func_def(self, o: 'mypy.nodes.FuncDef') -> None:
567 # Avoid C++ warnings by prepending [[noreturn]]
568 noreturn = _GetNoReturn(o.name)
569
570 virtual = ''
571 if self.virtual.IsVirtual(self.current_class_name, o.name):
572 virtual = 'virtual '
573
574 # declaration inside class { }
575 func_name = o.name
576
577 # Why can't we get this Type object with self.types[o]?
578 c_ret_type, _, _ = GetCReturnType(o.type.ret_type)
579
580 self.write_ind('%s%s%s %s(', noreturn, virtual, c_ret_type, func_name)
581
582 self._WriteFuncParams(o, write_defaults=True)
583 self.write(');\n')
584
585 def oils_visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
586 # In declarations, 'a.b' is only used for default argument
587 # values 'a::b'
588 self.accept(o.expr)
589 # TODO: remove write() in Decl pass
590 self.write('::')
591 self.write(o.name)
592
593 def oils_visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt',
594 lval: Expression, rval: Expression) -> None:
595 # Declare constant strings. They have to be at the top level.
596
597 # TODO: self.at_global_scope doesn't work for context managers and so forth
598 if self.indent == 0:
599 # Top level can't have foo.bar = baz
600 assert isinstance(lval, NameExpr), lval
601 if not util.SkipAssignment(lval.name):
602 c_type = GetCType(self.types[lval])
603 self.write('extern %s %s;\n', c_type, lval.name)
604
605 # TODO: we don't traverse here, so _CheckCondition() isn't called
606 # e.g. x = 'a' if mylist else 'b'
607
608 def oils_visit_constructor(self, o: ClassDef, stmt: FuncDef,
609 base_class_sym: util.SymbolPath) -> None:
610 self.indent += 1
611 self.write_ind('%s(', o.name)
612 self._WriteFuncParams(stmt, write_defaults=True)
613 self.write(');\n')
614 self.indent -= 1
615
616 def oils_visit_dunder_exit(self, o: ClassDef, stmt: FuncDef,
617 base_class_sym: util.SymbolPath) -> None:
618 self.indent += 1
619 # Turn it into a destructor with NO ARGS
620 self.write_ind('~%s();\n', o.name)
621 self.indent -= 1
622
623 def oils_visit_method(self, o: ClassDef, stmt: FuncDef,
624 base_class_sym: util.SymbolPath) -> None:
625 self.indent += 1
626 self.accept(stmt)
627 self.indent -= 1
628
629 def oils_visit_class_members(self, o: ClassDef,
630 base_class_sym: util.SymbolPath) -> None:
631 # Write member variables
632 self.indent += 1
633 self._MemberDecl(o, base_class_sym)
634 self.indent -= 1
635
636 def oils_visit_class_def(
637 self, o: 'mypy.nodes.ClassDef',
638 base_class_sym: Optional[util.SymbolPath]) -> None:
639 self.write_ind('class %s', o.name) # block after this
640
641 # e.g. class TextOutput : public ColorOutput
642 if base_class_sym:
643 self.write(' : public %s',
644 SymbolToString(base_class_sym, strip_package=True))
645
646 self.write(' {\n')
647 self.write_ind(' public:\n')
648
649 # This visits all the methods, with self.indent += 1, param
650 # base_class_sym, self.current_method_name
651
652 super().oils_visit_class_def(o, base_class_sym)
653
654 self.write_ind('};\n')
655 self.write('\n')
656
657 def _GcHeaderDecl(self, o: 'mypy.nodes.ClassDef',
658 field_gc: Tuple[str, str], mask_bits: List[str]) -> None:
659 if mask_bits:
660 self.write_ind('\n')
661 self.write_ind('static constexpr uint32_t field_mask() {\n')
662 self.write_ind(' return ')
663 for i, b in enumerate(mask_bits):
664 if i != 0:
665 self.write('\n')
666 self.write_ind(' | ')
667 self.write(b)
668 self.write(';\n')
669 self.write_ind('}\n')
670
671 obj_tag, obj_arg = field_gc
672 if obj_tag == 'HeapTag::FixedSize':
673 obj_mask = obj_arg
674 obj_header = 'ObjHeader::ClassFixed(%s, sizeof(%s))' % (obj_mask,
675 o.name)
676 elif obj_tag == 'HeapTag::Scanned':
677 num_pointers = obj_arg
678 obj_header = 'ObjHeader::ClassScanned(%s, sizeof(%s))' % (
679 num_pointers, o.name)
680 else:
681 raise AssertionError(o.name)
682
683 self.write('\n')
684 self.write_ind('static constexpr ObjHeader obj_header() {\n')
685 self.write_ind(' return %s;\n' % obj_header)
686 self.write_ind('}\n')
687
688 def _MemberDecl(self, o: 'mypy.nodes.ClassDef',
689 base_class_sym: util.SymbolPath) -> None:
690 member_vars = self.all_member_vars[o]
691
692 # List of field mask expressions
693 mask_bits = []
694 if self.virtual.CanReorderFields(SplitPyName(o.fullname)):
695 # No inheritance, so we are free to REORDER member vars, putting
696 # pointers at the front.
697
698 pointer_members = []
699 non_pointer_members = []
700
701 for name in member_vars:
702 _, c_type, is_managed = member_vars[name]
703 if is_managed:
704 pointer_members.append(name)
705 else:
706 non_pointer_members.append(name)
707
708 # So we declare them in the right order
709 sorted_member_names = pointer_members + non_pointer_members
710
711 field_gc = ('HeapTag::Scanned', str(len(pointer_members)))
712 else:
713 # Has inheritance
714
715 # The field mask of a derived class is unioned with its base's
716 # field mask.
717 if base_class_sym:
718 mask_bits.append(
719 '%s::field_mask()' %
720 SymbolToString(base_class_sym, strip_package=True))
721
722 for name in sorted(member_vars):
723 _, c_type, is_managed = member_vars[name]
724 if is_managed:
725 mask_bits.append('maskbit(offsetof(%s, %s))' %
726 (o.name, name))
727
728 # A base class with no fields has kZeroMask.
729 if not base_class_sym and not mask_bits:
730 mask_bits.append('kZeroMask')
731
732 sorted_member_names = sorted(member_vars)
733
734 field_gc = ('HeapTag::FixedSize', 'field_mask()')
735
736 # Write member variables
737
738 #log('MEMBERS for %s: %s', o.name, list(self.member_vars.keys()))
739 if len(member_vars):
740 if base_class_sym:
741 self.write('\n') # separate from functions
742
743 for name in sorted_member_names:
744 _, c_type, _ = member_vars[name]
745 # use default zero initialization for all members
746 # (context managers may be on the stack)
747 self.write_ind('%s %s{};\n', c_type, name)
748
749 # Context managers aren't GC objects
750 if not _IsContextManager(self.current_class_name):
751 self._GcHeaderDecl(o, field_gc, mask_bits)
752
753 self.write('\n')
754 self.write_ind('DISALLOW_COPY_AND_ASSIGN(%s)\n', o.name)
755
756
757class Impl(_Shared):
758
759 def __init__(
760 self,
761 types: Dict[Expression, Type],
762 global_strings: 'const_pass.GlobalStrings',
763 yield_out_params: Dict[FuncDef, Tuple[str, str]], # input
764 all_member_vars: Optional[AllMemberVars] = None,
765 local_vars: Optional[AllLocalVars] = None,
766 dot_exprs: Optional['conversion_pass.DotExprs'] = None,
767 stack_roots_warn: Optional[int] = None,
768 stack_roots: Optional[pass_state.StackRoots] = None) -> None:
769 _Shared.__init__(self,
770 types,
771 global_strings,
772 yield_out_params,
773 all_member_vars=all_member_vars)
774 self.local_vars = local_vars
775
776 # Computed in previous passes
777 self.dot_exprs = dot_exprs
778 self.stack_roots_warn = stack_roots_warn
779 self.stack_roots = stack_roots
780
781 # Traversal state used to to create an EAGER List<T>
782 self.yield_eager_assign: Dict[AssignmentStmt, Tuple[str, str]] = {}
783 self.yield_eager_for: Dict[ForStmt, Tuple[str, str]] = {}
784
785 self.yield_assign_node: Optional[AssignmentStmt] = None
786 self.yield_for_node: Optional[ForStmt] = None
787
788 # More Traversal state
789 self.current_func_node: Optional[FuncDef] = None
790
791 self.unique_id = 0
792
793 def _NamespaceComment(self) -> str:
794 # abstract method
795 return 'define'
796
797 def oils_visit_func_def(self, o: 'mypy.nodes.FuncDef') -> None:
798 if self.current_class_name:
799 # definition looks like
800 # void Class::method(...);
801 func_name = SymbolToString((self.current_class_name[-1], o.name))
802 noreturn = ''
803 else:
804 func_name = o.name
805 noreturn = _GetNoReturn(o.name)
806
807 self.write('\n')
808
809 # Why can't we get this Type object with self.types[o]?
810 c_ret_type, _, _ = GetCReturnType(o.type.ret_type)
811
812 self.write_ind('%s%s %s(', noreturn, c_ret_type, func_name)
813
814 self.current_func_node = o
815 self._WriteFuncParams(o, write_defaults=False)
816
817 self.write(') ')
818 arg_names = [arg.variable.name for arg in o.arguments]
819 #log('arg_names %s', arg_names)
820 #log('local_vars %s', self.local_vars[o])
821 local_var_list: List[LocalVar] = []
822 for (lval_name, lval_type) in self.local_vars[o]:
823 local_var_list.append((lval_name, lval_type, lval_name
824 in arg_names))
825
826 self.write('{\n')
827
828 self.indent += 1
829 self._WriteLocals(local_var_list)
830 self._WriteBody(o.body.body)
831 self.indent -= 1
832
833 self.write('}\n')
834
835 self.current_func_node = None
836
837 def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> None:
838 assert self.current_func_node in self.yield_out_params
839 self.write('%s->append(',
840 self.yield_out_params[self.current_func_node][0])
841 self.accept(o.expr)
842 self.write(')')
843
844 def _WriteArgList(self, args: List[Expression]) -> None:
845 self.write('(')
846 for i, arg in enumerate(args):
847 if i != 0:
848 self.write(', ')
849 self.accept(arg)
850
851 # Pass an extra arg like my_generator(42, &accum)
852 #
853 # Two cases:
854 # ForStmt: for y in generator(42): =>
855 # generator(42, &y)
856 # AssignmentStmt: it = generator(42) =>
857 # List<int> _iter_buf_it;
858 # generator(42, &iter_buf_it); # eagerly append
859
860 eager_pair = (self.yield_eager_assign.get(self.yield_assign_node) or
861 self.yield_eager_for.get(self.yield_for_node))
862
863 if eager_pair:
864 if len(args) > 0:
865 self.write(', ')
866
867 eager_list_name, _ = eager_pair
868 self.write('&%s', eager_list_name)
869
870 self.write(')')
871
872 def oils_visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
873 dot_expr = self.dot_exprs[o]
874
875 if isinstance(dot_expr, pass_state.StackObjectMember):
876 op = '.'
877
878 elif (isinstance(dot_expr, pass_state.StaticClassMember) or
879 isinstance(dot_expr, pass_state.ModuleMember)):
880 op = '::'
881
882 elif isinstance(dot_expr, pass_state.HeapObjectMember):
883 op = '->'
884
885 else:
886 raise AssertionError()
887
888 self.accept(o.expr)
889 self.write(op)
890
891 if o.name == 'errno':
892 # e->errno -> e->errno_ to avoid conflict with C macro
893 self.write('errno_')
894 else:
895 self.write('%s', o.name)
896
897 def _IsInstantiation(self, o: 'mypy.nodes.CallExpr') -> bool:
898 callee_name = o.callee.name
899 callee_type = self.types[o.callee]
900
901 # e.g. int() takes str, float, etc. It doesn't matter for translation.
902 if isinstance(callee_type, Overloaded):
903 if 0:
904 for item in callee_type.items():
905 self.log('item: %s', item)
906
907 if isinstance(callee_type, CallableType):
908 # If the function name is the same as the return type, then add
909 # 'Alloc<>'. f = Foo() => f = Alloc<Foo>().
910 ret_type = callee_type.ret_type
911
912 # e.g. str(i) is a free function
913 if (callee_name not in ('str', 'bool', 'float') and
914 'BigInt' not in callee_name and
915 isinstance(ret_type, Instance)):
916
917 ret_type_name = ret_type.type.name
918
919 # HACK: Const is the callee; expr__Const is the return type
920 if (ret_type_name == callee_name or
921 ret_type_name.endswith('__' + callee_name)):
922 return True
923
924 return False
925
926 def oils_visit_probe_call(self, o: 'mypy.nodes.CallExpr') -> None:
927 assert len(o.args) >= 2 and len(o.args) < 13, o.args
928 assert isinstance(o.args[0], mypy.nodes.StrExpr), o.args[0]
929 assert isinstance(o.args[1], mypy.nodes.StrExpr), o.args[1]
930 arity = len(o.args) - 2
931 macro = 'DTRACE_PROBE'
932 if arity > 0:
933 macro = 'DTRACE_PROBE%d' % arity
934
935 self.write('%s(%s, %s', macro, o.args[0].value, o.args[1].value)
936
937 for arg in o.args[2:]:
938 arg_type = self.types[arg]
939 self.write(', ')
940 if util.IsStr(arg_type): # TODO: doesn't know it's an Instance
941 self.write('%s->data()' % arg.name)
942 else:
943 self.accept(arg)
944
945 self.write(')')
946
947 def oils_visit_log_call(self, fmt: StrExpr,
948 args: List[Expression]) -> None:
949 if len(args) == 0: # log('const') -> print_stderr(S_xyz)
950 # This is a GC string
951 self.write('mylib::print_stderr(')
952 self.accept(fmt)
953 self.write(')')
954 return
955
956 # log('const %s', a) -> print_stderr(StrFormat("const %s", a))
957 quoted_fmt = PythonStringLiteral(fmt.value)
958 self.write('mylib::print_stderr(StrFormat(%s, ' % quoted_fmt)
959
960 for i, arg in enumerate(args):
961 if i != 0:
962 self.write(', ')
963 self.accept(arg)
964 self.write('))')
965
966 def oils_visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> None:
967 callee_name = o.callee.name
968
969 # return cast(ShArrayLiteral, tok)
970 # -> return static_cast<ShArrayLiteral*>(tok)
971
972 # TODO: Consolidate this with AssignmentExpr logic.
973 if callee_name == 'cast':
974 call = o
975 type_expr = call.args[0]
976
977 subtype_name = _GetCTypeForCast(type_expr)
978 cast_kind = _GetCastKind(self.module_path, subtype_name)
979 self.write('%s<%s>(', cast_kind, subtype_name)
980 self.accept(call.args[1]) # variable being casted
981 self.write(')')
982 return
983
984 if isinstance(o.callee, MemberExpr) and callee_name == 'next':
985 self.accept(o.callee.expr)
986 self.write('.iterNext')
987 self._WriteArgList(o.args)
988 return
989
990 if self._IsInstantiation(o):
991 self.write('Alloc<')
992 self.accept(o.callee)
993 self.write('>')
994 self._WriteArgList(o.args)
995 return
996
997 # Namespace.
998 if callee_name == 'int': # int('foo') in Python conflicts with keyword
999 self.write('to_int')
1000 elif callee_name == 'float':
1001 self.write('to_float')
1002 elif callee_name == 'bool':
1003 self.write('to_bool')
1004 else:
1005 self.accept(o.callee) # could be f() or obj.method()
1006
1007 self._WriteArgList(o.args)
1008
1009 # TODO: we could check that keyword arguments are passed as named args?
1010 #self.log(' arg_kinds %s', o.arg_kinds)
1011 #self.log(' arg_names %s', o.arg_names)
1012
1013 def oils_visit_format_expr(self, left: Expression,
1014 right: Expression) -> None:
1015 self.write('StrFormat(')
1016 if isinstance(left, StrExpr):
1017 self.write(PythonStringLiteral(left.value))
1018 else:
1019 self.accept(left)
1020 #log('right_type %s', right_type)
1021
1022 right_type = self.types[right]
1023
1024 # TODO: Can we restore some type checking?
1025 if 0:
1026 if isinstance(right_type, Instance):
1027 fmt_types: List[Type] = [right_type]
1028 elif isinstance(right_type, TupleType):
1029 fmt_types = right_type.items
1030 # Handle Optional[str]
1031 elif (isinstance(right_type, UnionType) and
1032 len(right_type.items) == 2 and
1033 isinstance(right_type.items[1], NoneTyp)):
1034 fmt_types = [right_type.items[0]]
1035 else:
1036 raise AssertionError(right_type)
1037
1038 # In the definition pass, write the call site.
1039 if isinstance(right_type, TupleType):
1040 assert isinstance(right, TupleExpr), right
1041 for i, item in enumerate(right.items):
1042 self.write(', ')
1043 self.accept(item)
1044
1045 else: # '[%s]' % x
1046 self.write(', ')
1047 self.accept(right)
1048
1049 self.write(')')
1050
1051 def oils_visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
1052 # a + b when a and b are strings. (Can't use operator overloading
1053 # because they're pointers.)
1054 left_type = self.types[o.left]
1055 right_type = self.types[o.right]
1056
1057 # NOTE: Need GetCType to handle Optional[BigStr*] in ASDL schemas.
1058 # Could tighten it up later.
1059 left_ctype = GetCType(left_type)
1060 right_ctype = GetCType(right_type)
1061
1062 c_op = o.op
1063 if left_ctype == right_ctype == 'int' and c_op == '//':
1064 # integer division // -> /
1065 c_op = '/'
1066
1067 # 'abc' + 'def'
1068 if left_ctype == right_ctype == 'BigStr*' and c_op == '+':
1069 self.write('str_concat(')
1070 self.accept(o.left)
1071 self.write(', ')
1072 self.accept(o.right)
1073 self.write(')')
1074 return
1075
1076 # 'abc' * 3
1077 if left_ctype == 'BigStr*' and right_ctype == 'int' and c_op == '*':
1078 self.write('str_repeat(')
1079 self.accept(o.left)
1080 self.write(', ')
1081 self.accept(o.right)
1082 self.write(')')
1083 return
1084
1085 # [None] * 3 => list_repeat(None, 3)
1086 if (left_ctype.startswith('List<') and right_ctype == 'int' and
1087 c_op == '*'):
1088 self.write('list_repeat(')
1089 self.accept(o.left.items[0])
1090 self.write(', ')
1091 self.accept(o.right)
1092 self.write(')')
1093 return
1094
1095 # These parens are sometimes extra, but sometimes required. Example:
1096 #
1097 # if ((a and (false or true))) { # right
1098 # vs.
1099 # if (a and false or true)) { # wrong
1100 self.write('(')
1101 self.accept(o.left)
1102 self.write(' %s ', c_op)
1103 self.accept(o.right)
1104 self.write(')')
1105
1106 def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> None:
1107 # Make sure it's binary
1108 assert len(o.operators) == 1, o.operators
1109 assert len(o.operands) == 2, o.operands
1110
1111 operator = o.operators[0]
1112 left = o.operands[0]
1113 right = o.operands[1]
1114
1115 # Assume is and is not are for None / nullptr comparison.
1116 if operator == 'is': # foo is None => foo == nullptr
1117 self.accept(o.operands[0])
1118 self.write(' == ')
1119 self.accept(o.operands[1])
1120 return
1121
1122 if operator == 'is not': # foo is not None => foo != nullptr
1123 self.accept(o.operands[0])
1124 self.write(' != ')
1125 self.accept(o.operands[1])
1126 return
1127
1128 t0 = self.types[left]
1129 t1 = self.types[right]
1130
1131 # 0: not a special case
1132 # 1: str
1133 # 2: Optional[str] which is Union[str, None]
1134 left_type_i = 0 # not a special case
1135 right_type_i = 0 # not a special case
1136
1137 if util.IsStr(t0):
1138 left_type_i = 1
1139 elif (isinstance(t0, UnionType) and len(t0.items) == 2 and
1140 util.IsStr(t0.items[0]) and isinstance(t0.items[1], NoneTyp)):
1141 left_type_i = 2
1142
1143 if util.IsStr(t1):
1144 right_type_i = 1
1145 elif (isinstance(t1, UnionType) and len(t1.items) == 2 and
1146 util.IsStr(t1.items[0]) and isinstance(t1.items[1], NoneTyp)):
1147 right_type_i = 2
1148
1149 #self.log('left_type_i %s right_type_i %s', left_type, right_type)
1150
1151 if left_type_i > 0 and right_type_i > 0 and operator in ('==', '!='):
1152 if operator == '!=':
1153 self.write('!(')
1154
1155 # NOTE: This could also be str_equals(left, right)? Does it make a
1156 # difference?
1157 if left_type_i > 1 or right_type_i > 1:
1158 self.write('maybe_str_equals(')
1159 else:
1160 self.write('str_equals(')
1161 self.accept(left)
1162 self.write(', ')
1163 self.accept(right)
1164 self.write(')')
1165
1166 if operator == '!=':
1167 self.write(')')
1168 return
1169
1170 # Note: we could get rid of this altogether and rely on C++ function
1171 # overloading. But somehow I like it more explicit, closer to C (even
1172 # though we use templates).
1173 contains_func = _ContainsFunc(t1)
1174
1175 if operator == 'in':
1176 if isinstance(right, TupleExpr):
1177 left_type = self.types[left]
1178
1179 equals_func = _EqualsFunc(left_type)
1180
1181 # x in (1, 2, 3) => (x == 1 || x == 2 || x == 3)
1182 self.write('(')
1183
1184 for i, item in enumerate(right.items):
1185 if i != 0:
1186 self.write(' || ')
1187
1188 if equals_func:
1189 self.write('%s(' % equals_func)
1190 self.accept(left)
1191 self.write(', ')
1192 self.accept(item)
1193 self.write(')')
1194 else:
1195 self.accept(left)
1196 self.write(' == ')
1197 self.accept(item)
1198
1199 self.write(')')
1200 return
1201
1202 assert contains_func, "RHS of 'in' has type %r" % t1
1203 # x in mylist => list_contains(mylist, x)
1204 self.write('%s(', contains_func)
1205 self.accept(right)
1206 self.write(', ')
1207 self.accept(left)
1208 self.write(')')
1209 return
1210
1211 if operator == 'not in':
1212 if isinstance(right, TupleExpr):
1213 left_type = self.types[left]
1214 equals_func = _EqualsFunc(left_type)
1215
1216 # x not in (1, 2, 3) => (x != 1 && x != 2 && x != 3)
1217 self.write('(')
1218
1219 for i, item in enumerate(right.items):
1220 if i != 0:
1221 self.write(' && ')
1222
1223 if equals_func:
1224 self.write('!%s(' % equals_func)
1225 self.accept(left)
1226 self.write(', ')
1227 self.accept(item)
1228 self.write(')')
1229 else:
1230 self.accept(left)
1231 self.write(' != ')
1232 self.accept(item)
1233
1234 self.write(')')
1235 return
1236
1237 assert contains_func, t1
1238
1239 # x not in mylist => !list_contains(mylist, x)
1240 self.write('!%s(', contains_func)
1241 self.accept(right)
1242 self.write(', ')
1243 self.accept(left)
1244 self.write(')')
1245 return
1246
1247 # Default case
1248 self.accept(o.operands[0])
1249 self.write(' %s ', o.operators[0])
1250 self.accept(o.operands[1])
1251
1252 def _WriteListElements(self,
1253 items: List[Expression],
1254 sep: str = ', ') -> None:
1255 # sep may be 'COMMA' for a macro
1256 self.write('{')
1257 for i, item in enumerate(items):
1258 if i != 0:
1259 self.write(sep)
1260 self.accept(item)
1261 self.write('}')
1262
1263 def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> None:
1264 list_type = self.types[o]
1265 # Note: need a lookup function that understands ListExpr -> Instance
1266 assert isinstance(list_type, Instance), list_type
1267
1268 #self.log('**** list_type = %s', list_type)
1269 c_type = GetCType(list_type)
1270
1271 item_type = list_type.args[0] # int for List[int]
1272 item_c_type = GetCType(item_type)
1273
1274 assert c_type.endswith('*'), c_type
1275 c_type = c_type[:-1] # HACK TO CLEAN UP
1276
1277 if len(o.items) == 0:
1278 self.write('Alloc<%s>()' % c_type)
1279 else:
1280 self.write('NewList<%s>(std::initializer_list<%s>' %
1281 (item_c_type, item_c_type))
1282 self._WriteListElements(o.items)
1283 self.write(')')
1284
1285 def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> None:
1286 dict_type = self.types[o]
1287 # Note: need a lookup function that understands DictExpr -> Instance
1288 assert isinstance(dict_type, Instance), dict_type
1289
1290 c_type = GetCType(dict_type)
1291 assert c_type.endswith('*'), c_type
1292 c_type = c_type[:-1] # HACK TO CLEAN UP
1293
1294 key_type, val_type = dict_type.args
1295 key_c_type = GetCType(key_type)
1296 val_c_type = GetCType(val_type)
1297
1298 self.write('Alloc<%s>(' % c_type)
1299 #self.write('NewDict<%s, %s>(' % (key_c_type, val_c_type))
1300 if o.items:
1301 keys = [k for k, _ in o.items]
1302 values = [v for _, v in o.items]
1303
1304 self.write('std::initializer_list<%s>' % key_c_type)
1305 self._WriteListElements(keys)
1306 self.write(', ')
1307
1308 self.write('std::initializer_list<%s>' % val_c_type)
1309 self._WriteListElements(values)
1310
1311 self.write(')')
1312
1313 def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> None:
1314 tuple_type = self.types[o]
1315 c_type = GetCType(tuple_type)
1316 assert c_type.endswith('*'), c_type
1317 c_type = c_type[:-1] # HACK TO CLEAN UP
1318
1319 self.write('(Alloc<%s>(' % c_type)
1320 for i, item in enumerate(o.items):
1321 if i != 0:
1322 self.write(', ')
1323 self.accept(item)
1324 self.write('))')
1325
1326 def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> None:
1327 self.accept(o.base)
1328
1329 #base_type = self.types[o.base]
1330 #self.log('*** BASE TYPE %s', base_type)
1331
1332 if isinstance(o.index, SliceExpr):
1333 self.accept(o.index) # method call
1334 else:
1335 # it's hard syntactically to do (*a)[0], so do it this way.
1336 if util.SMALL_STR:
1337 self.write('.at(')
1338 else:
1339 self.write('->at(')
1340
1341 self.accept(o.index)
1342 self.write(')')
1343
1344 def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> None:
1345 self.write('->slice(')
1346 if o.begin_index:
1347 self.accept(o.begin_index)
1348 else:
1349 self.write('0') # implicit beginning
1350
1351 if o.end_index:
1352 self.write(', ')
1353 self.accept(o.end_index)
1354
1355 if o.stride:
1356 if not o.begin_index or not o.end_index:
1357 raise AssertionError(
1358 'Stride only supported with beginning and ending index')
1359
1360 self.write(', ')
1361 self.accept(o.stride)
1362
1363 self.write(')')
1364
1365 def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> None:
1366 if not _CheckCondition(o.cond, self.types):
1367 self.report_error(
1368 o,
1369 "Use explicit len(obj) or 'obj is not None' for mystr, mylist, mydict"
1370 )
1371 return
1372
1373 # 0 if b else 1 -> b ? 0 : 1
1374 self.accept(o.cond)
1375 self.write(' ? ')
1376 self.accept(o.if_expr)
1377 self.write(' : ')
1378 self.accept(o.else_expr)
1379
1380 def _WriteTupleUnpacking(self,
1381 temp_name: str,
1382 lval_items: List[Expression],
1383 item_types: List[Type],
1384 is_return: bool = False) -> None:
1385 """Used by assignment and for loops.
1386
1387 is_return is a special case for:
1388
1389 # return Tuple2<A, B> by VALUE, not Tuple2<A, B>* pointer
1390 a, b = myfunc()
1391 """
1392 for i, (lval_item, item_type) in enumerate(zip(lval_items,
1393 item_types)):
1394 if isinstance(lval_item, NameExpr):
1395 if util.SkipAssignment(lval_item.name):
1396 continue
1397 self.write_ind('%s', lval_item.name)
1398 else:
1399 # Could be MemberExpr like self.foo, self.bar = baz
1400 self.write_ind('')
1401 self.accept(lval_item)
1402
1403 # Tuples that are return values aren't pointers
1404 op = '.' if is_return else '->'
1405 self.write(' = %s%sat%d();\n', temp_name, op, i) # RHS
1406
1407 def _WriteTupleUnpackingInLoop(self, temp_name: str,
1408 lval_items: List[Expression],
1409 item_types: List[Type]) -> None:
1410 for i, (lval_item, item_type) in enumerate(zip(lval_items,
1411 item_types)):
1412 c_item_type = GetCType(item_type)
1413
1414 if isinstance(lval_item, NameExpr):
1415 if util.SkipAssignment(lval_item.name):
1416 continue
1417
1418 self.write_ind('%s %s', c_item_type, lval_item.name)
1419 else:
1420 # Could be MemberExpr like self.foo, self.bar = baz
1421 self.write_ind('')
1422 self.accept(lval_item)
1423
1424 op = '->'
1425 self.write(' = %s%sat%d();\n', temp_name, op, i) # RHS
1426
1427 # Note: it would be nice to eliminate these roots, just like
1428 # StackRoots _for() below
1429 if isinstance(lval_item, NameExpr):
1430 if CTypeIsManaged(c_item_type) and not self.stack_roots:
1431 self.write_ind('StackRoot _unpack_%d(&%s);\n' %
1432 (i, lval_item.name))
1433
1434 def _AssignNewDictImpl(self, lval: Expression, prefix: str = '') -> None:
1435 """Translate NewDict() -> Alloc<Dict<K, V>>
1436
1437 This function is a specal case because the RHS need TYPES from the LHS.
1438
1439 e.g. here is how we make ORDERED dictionaries, which can't be done with {}:
1440
1441 d = NewDict() # type: Dict[int, int]
1442
1443 -> one of
1444
1445 auto* d = Alloc<Dict<int, int>>(); # declare
1446 d = Alloc<Dict<int, int>>(); # mutate
1447
1448 We also have:
1449
1450 self.d = NewDict()
1451 ->
1452 this->d = Alloc<Dict<int, int>)();
1453 """
1454 lval_type = self.types[lval]
1455 #self.log('lval type %s', lval_type)
1456
1457 # Fix for Dict[str, value]? in ASDL
1458 if (isinstance(lval_type, UnionType) and len(lval_type.items) == 2 and
1459 isinstance(lval_type.items[1], NoneTyp)):
1460 lval_type = lval_type.items[0]
1461
1462 c_type = GetCType(lval_type)
1463 assert c_type.endswith('*')
1464 self.write('Alloc<%s>()', c_type[:-1])
1465
1466 def _AssignCastImpl(self, lval: Expression, rval: CallExpr) -> None:
1467 """
1468 is_downcast_and_shadow idiom:
1469
1470 src = cast(source__SourcedFile, UP_src)
1471 -> source__SourcedFile* src = static_cast<source__SourcedFile>(UP_src)
1472 """
1473 assert isinstance(lval, NameExpr)
1474 type_expr = rval.args[0]
1475 subtype_name = _GetCTypeForCast(type_expr)
1476
1477 cast_kind = _GetCastKind(self.module_path, subtype_name)
1478
1479 is_downcast_and_shadow = False
1480 to_cast = rval.args[1]
1481 if isinstance(to_cast, NameExpr):
1482 if to_cast.name.startswith('UP_'):
1483 is_downcast_and_shadow = True
1484
1485 if is_downcast_and_shadow:
1486 # Declare NEW local variable inside case, which shadows it
1487 self.write_ind('%s %s = %s<%s>(', subtype_name, lval.name,
1488 cast_kind, subtype_name)
1489 else:
1490 # Normal variable
1491 self.write_ind('%s = %s<%s>(', lval.name, cast_kind, subtype_name)
1492
1493 self.accept(rval.args[1]) # variable being casted
1494 self.write(');\n')
1495
1496 def _AssignToGenerator(self, o: 'mypy.nodes.AssignmentStmt',
1497 lval: Expression, rval_type: Instance) -> None:
1498 """
1499 it_f = f(42)
1500
1501 translates to
1502
1503 List<int> _iter_buf_it;
1504 f(42, &_iter_buf_it);
1505 """
1506 # We're calling a generator. Create a temporary List<T> on the stack
1507 # to accumulate the results in one big batch, then wrap it in
1508 # ListIter<T>.
1509 assert len(rval_type.args) == 1, rval_type.args
1510 c_type = GetCType(rval_type)
1511
1512 type_param = rval_type.args[0]
1513 inner_c_type = GetCType(type_param)
1514
1515 assert isinstance(lval, NameExpr), lval
1516 eager_list_name = 'YIELD_%s' % lval.name
1517 eager_list_type = 'List<%s>*' % inner_c_type
1518
1519 # write the variable to accumulate into
1520 self.write_ind('List<%s> %s;\n', inner_c_type, eager_list_name)
1521
1522 # AssignmentStmt key, like:
1523 # it_f = f()
1524 # maybe call them self.generator_func, generator_assign
1525 # In MyPy, the type is Iterator though
1526 self.yield_eager_assign[o] = (eager_list_name, eager_list_type)
1527 self.write_ind('')
1528
1529 self.yield_assign_node = o # AssignmentStmt
1530 self.accept(o.rvalue)
1531 self.yield_assign_node = None
1532
1533 self.write(';\n')
1534
1535 self.write_ind('%s %s(&%s);\n', c_type, lval.name, eager_list_name)
1536
1537 def oils_visit_assign_to_listcomp(self, lval: NameExpr,
1538 left_expr: Expression,
1539 index_expr: Expression, seq: Expression,
1540 cond: Expression) -> None:
1541 """
1542 Special case for list comprehensions. Note that the LHS MUST be on the
1543 LHS, so we can append to it.
1544
1545 y = [i+1 for i in x[1:] if i]
1546 =>
1547 y = []
1548 for i in x[1:]:
1549 if i:
1550 y.append(i+1)
1551 (but in C++)
1552 """
1553 self.write_ind('%s = ', lval.name)
1554
1555 # BUG: can't use this to filter
1556 # results = [x for x in results]
1557 if isinstance(seq, NameExpr) and seq.name == lval.name:
1558 raise AssertionError(
1559 "Can't use var %r in list comprehension because it would "
1560 "be overwritten" % lval.name)
1561
1562 c_type = GetCType(self.types[lval])
1563 # Write empty container as initialization.
1564 assert c_type.endswith('*'), c_type # Hack
1565 self.write('Alloc<%s>();\n' % c_type[:-1])
1566
1567 over_type = self.types[seq]
1568 assert isinstance(over_type, Instance), over_type
1569
1570 if over_type.type.fullname == 'builtins.list':
1571 c_type = GetCType(over_type)
1572 # remove *
1573 assert c_type.endswith('*'), c_type
1574 c_iter_type = c_type.replace('List', 'ListIter', 1)[:-1]
1575 else:
1576 # List comprehension over dictionary not implemented
1577 c_iter_type = 'TODO_DICT'
1578
1579 self.write_ind('for (%s it(', c_iter_type)
1580 self.accept(seq)
1581 self.write('); !it.Done(); it.Next()) {\n')
1582
1583 item_type = over_type.args[0] # get 'int' from 'List<int>'
1584
1585 if isinstance(item_type, Instance):
1586 self.write_ind(' %s ', GetCType(item_type))
1587 # TODO(StackRoots): for ch in 'abc'
1588 self.accept(index_expr)
1589 self.write(' = it.Value();\n')
1590
1591 elif isinstance(item_type, TupleType): # [x for x, y in pairs]
1592 c_item_type = GetCType(item_type)
1593
1594 if isinstance(index_expr, TupleExpr):
1595 temp_name = 'tup%d' % self.unique_id
1596 self.unique_id += 1
1597 self.write_ind(' %s %s = it.Value();\n', c_item_type,
1598 temp_name)
1599
1600 self.indent += 1
1601
1602 # list comp
1603 self._WriteTupleUnpackingInLoop(temp_name, index_expr.items,
1604 item_type.items)
1605
1606 self.indent -= 1
1607 else:
1608 raise AssertionError()
1609
1610 else:
1611 raise AssertionError('Unexpected type %s' % item_type)
1612
1613 if cond is not None:
1614 self.indent += 1
1615 self.write_ind('if (')
1616 self.accept(cond)
1617 self.write(') {\n')
1618
1619 self.write_ind(' %s->append(', lval.name)
1620 self.accept(left_expr)
1621 self.write(');\n')
1622
1623 if cond:
1624 self.write_ind('}\n')
1625 self.indent -= 1
1626
1627 self.write_ind('}\n')
1628
1629 def oils_visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt',
1630 lval: Expression, rval: Expression) -> None:
1631
1632 # GLOBAL CONSTANTS - Avoid Alloc<T>, since that can't be done until main().
1633 if self.indent == 0:
1634 assert isinstance(lval, NameExpr), lval
1635 if util.SkipAssignment(lval.name):
1636 return
1637 #self.log(' GLOBAL: %s', lval.name)
1638
1639 lval_type = self.types[lval]
1640
1641 # Global
1642 # L = [1, 2] # type: List[int]
1643 if isinstance(rval, ListExpr):
1644 assert isinstance(lval_type, Instance), lval_type
1645
1646 item_type = lval_type.args[0]
1647 item_c_type = GetCType(item_type)
1648
1649 # Any constant strings will have already been written
1650 # TODO: Assert that every item is a constant?
1651 self.write('GLOBAL_LIST(%s, %s, %d, ', lval.name, item_c_type,
1652 len(rval.items))
1653
1654 self._WriteListElements(rval.items, sep=' COMMA ')
1655
1656 self.write(');\n')
1657 return
1658
1659 # Global
1660 # D = {"foo": "bar"} # type: Dict[str, str]
1661 if isinstance(rval, DictExpr):
1662 assert isinstance(lval_type, Instance), lval_type
1663
1664 key_type, val_type = lval_type.args
1665
1666 key_c_type = GetCType(key_type)
1667 val_c_type = GetCType(val_type)
1668
1669 dict_expr = rval
1670 self.write('GLOBAL_DICT(%s, %s, %s, %d, ', lval.name,
1671 key_c_type, val_c_type, len(dict_expr.items))
1672
1673 keys = [k for k, _ in dict_expr.items]
1674 values = [v for _, v in dict_expr.items]
1675
1676 self._WriteListElements(keys, sep=' COMMA ')
1677 self.write(', ')
1678 self._WriteListElements(values, sep=' COMMA ')
1679
1680 self.write(');\n')
1681 return
1682
1683 # We could do GcGlobal<> for ASDL classes, but Oils doesn't use them
1684 if isinstance(rval, CallExpr):
1685 self.report_error(
1686 o,
1687 "Can't initialize objects at the top level, only BigStr List Dict"
1688 )
1689 return
1690
1691 # myconst = 1 << 3 => myconst = 1 << 3 is currently allowed
1692
1693 #
1694 # Non-top-level
1695 #
1696
1697 if isinstance(rval, CallExpr):
1698 callee = rval.callee
1699 callee_name = callee.name
1700
1701 if callee_name == 'NewDict':
1702 self.write_ind('')
1703
1704 # Hack for non-members - why does this work?
1705 # Tests cases in mycpp/examples/containers.py
1706 if (not isinstance(lval, MemberExpr) and
1707 self.current_func_node is None):
1708 self.write('auto* ')
1709
1710 self.accept(lval)
1711 self.write(' = ')
1712 self._AssignNewDictImpl(lval) # uses lval, not rval
1713 self.write(';\n')
1714 return
1715
1716 if callee_name == 'cast':
1717 self._AssignCastImpl(lval, rval)
1718 return
1719
1720 rval_type = self.types[rval]
1721 if (isinstance(rval_type, Instance) and
1722 rval_type.type.fullname == 'typing.Iterator'):
1723 self._AssignToGenerator(o, lval, rval_type)
1724 return
1725
1726 if isinstance(lval, NameExpr):
1727 lval_type = self.types[lval]
1728 #c_type = GetCType(lval_type, local=self.indent != 0)
1729 c_type = GetCType(lval_type)
1730
1731 if self.at_global_scope:
1732 # globals always get a type -- they're not mutated
1733 self.write_ind('%s %s = ', c_type, lval.name)
1734 else:
1735 # local declarations are "hoisted" to the top of the function
1736 self.write_ind('%s = ', lval.name)
1737
1738 self.accept(rval)
1739 self.write(';\n')
1740 return
1741
1742 if isinstance(lval, MemberExpr): # self.x = foo
1743 self.write_ind('')
1744 self.accept(lval)
1745 self.write(' = ')
1746 self.accept(rval)
1747 self.write(';\n')
1748 return
1749
1750 if isinstance(lval, IndexExpr): # a[x] = 1
1751 # d->set(x, 1) for both List and Dict
1752 self.write_ind('')
1753 self.accept(lval.base)
1754 self.write('->set(')
1755 self.accept(lval.index)
1756 self.write(', ')
1757 self.accept(rval)
1758 self.write(');\n')
1759 return
1760
1761 if isinstance(lval, TupleExpr):
1762 # An assignment to an n-tuple turns into n+1 statements. Example:
1763 #
1764 # x, y = mytuple
1765 #
1766 # Tuple2<int, BigStr*> tup1 = mytuple
1767 # int x = tup1->at0()
1768 # BigStr* y = tup1->at1()
1769
1770 rvalue_type = self.types[rval]
1771
1772 # type alias upgrade for MyPy 0.780
1773 if isinstance(rvalue_type, TypeAliasType):
1774 rvalue_type = rvalue_type.alias.target
1775
1776 assert isinstance(rvalue_type, TupleType), rvalue_type
1777
1778 c_type = GetCType(rvalue_type)
1779
1780 is_return = (isinstance(rval, CallExpr) and
1781 rval.callee.name != "next")
1782 if is_return:
1783 assert c_type.endswith('*')
1784 c_type = c_type[:-1]
1785
1786 temp_name = 'tup%d' % self.unique_id
1787 self.unique_id += 1
1788 self.write_ind('%s %s = ', c_type, temp_name)
1789
1790 self.accept(rval)
1791 self.write(';\n')
1792
1793 # assignment
1794 self._WriteTupleUnpacking(temp_name,
1795 lval.items,
1796 rvalue_type.items,
1797 is_return=is_return)
1798 return
1799
1800 raise AssertionError(lval)
1801
1802 def _WriteBody(self, body: List[Statement]) -> None:
1803 """Write a block without the { }."""
1804 for stmt in body:
1805 self.accept(stmt)
1806
1807 def oils_visit_for_stmt(self, o: 'mypy.nodes.ForStmt',
1808 func_name: Optional[str]) -> None:
1809 if 0:
1810 self.log('ForStmt')
1811 self.log(' index_type %s', o.index_type)
1812 self.log(' inferred_item_type %s', o.inferred_item_type)
1813 self.log(' inferred_iterator_type %s', o.inferred_iterator_type)
1814
1815 if func_name:
1816 assert isinstance(o.expr, CallExpr), o.expr # caller ensured it
1817 args = o.expr.args
1818
1819 # special case: 'for i in xrange(3)'
1820 if func_name == 'xrange':
1821 assert isinstance(o.index, NameExpr), o.index
1822 index_name = o.index.name
1823
1824 assert isinstance(o.expr, CallExpr), o.expr # caller ensured it
1825 num_args = len(args)
1826
1827 if num_args == 1: # xrange(end)
1828 self.write_ind('for (int %s = 0; %s < ', index_name,
1829 index_name)
1830 self.accept(args[0])
1831 self.write('; ++%s) ', index_name)
1832
1833 elif num_args == 2: # xrange(being, end)
1834 self.write_ind('for (int %s = ', index_name)
1835 self.accept(args[0])
1836 self.write('; %s < ', index_name)
1837 self.accept(args[1])
1838 self.write('; ++%s) ', index_name)
1839
1840 elif num_args == 3: # xrange(being, end, step)
1841 # Special case to detect a step of -1. This is a static
1842 # heuristic, because it could be negative dynamically.
1843 # TODO: could add an API like mylib.reverse_xrange()
1844 step = args[2]
1845 if isinstance(step, UnaryExpr) and step.op == '-':
1846 comparison_op = '>'
1847 else:
1848 comparison_op = '<'
1849
1850 self.write_ind('for (int %s = ', index_name)
1851 self.accept(args[0])
1852 self.write('; %s %s ', index_name, comparison_op)
1853 self.accept(args[1])
1854 self.write('; %s += ', index_name)
1855 self.accept(step)
1856 self.write(') ')
1857
1858 else:
1859 raise AssertionError()
1860
1861 self.accept(o.body)
1862 return
1863
1864 reverse = False
1865
1866 # for i, x in enumerate(...):
1867 index0_name = None
1868 if func_name == 'enumerate':
1869 assert isinstance(o.index, TupleExpr), o.index
1870 index0 = o.index.items[0]
1871
1872 assert isinstance(index0, NameExpr), index0
1873 index0_name = index0.name # generate int i = 0; ; ++i
1874
1875 # Get type of 'x' in 'for i, x in enumerate(...)'
1876 assert isinstance(o.inferred_item_type,
1877 TupleType), o.inferred_item_type
1878 item_type = o.inferred_item_type.items[1]
1879 index_expr = o.index.items[1]
1880
1881 assert isinstance(o.expr, CallExpr), o.expr # caller ensured
1882 # enumerate(mylist) turns into iteration over mylist with variable i
1883 assert len(args) == 1, args
1884 iterated_over = args[0]
1885
1886 elif func_name == 'reversed':
1887 # NOTE: enumerate() and reversed() can't be mixed yet. But you CAN
1888 # reverse iter over tuples.
1889 item_type = o.inferred_item_type
1890 index_expr = o.index
1891
1892 assert len(args) == 1, args
1893 iterated_over = args[0]
1894
1895 reverse = True # use different iterate
1896
1897 elif func_name == 'iteritems':
1898 item_type = o.inferred_item_type
1899 index_expr = o.index
1900
1901 assert len(args) == 1, args
1902 # This should be a dict
1903 iterated_over = args[0]
1904
1905 #log('------------ ITERITEMS OVER %s', iterated_over)
1906
1907 else:
1908 item_type = o.inferred_item_type
1909 index_expr = o.index
1910 iterated_over = o.expr
1911
1912 over_type = self.types[iterated_over]
1913
1914 if isinstance(over_type, TypeAliasType):
1915 over_type = over_type.alias.target
1916
1917 assert isinstance(over_type, Instance), over_type
1918
1919 if 0:
1920 log("***** OVER %s %s", over_type, dir(over_type))
1921 t = over_type.type
1922 log("***** t %s %s", t, dir(t))
1923 bases = t.bases
1924 # Look for string and dict!
1925 log("=== bases %s %s", bases, dir(bases))
1926
1927 #self.log(' iterating over type %s', over_type)
1928 #self.log(' iterating over type %s', over_type.type.fullname)
1929
1930 eager_list_name: Optional[str] = None
1931
1932 over_list = False
1933 over_dict = False
1934
1935 if over_type.type.fullname == 'builtins.list':
1936 over_list = True
1937 container_base_type = over_type
1938
1939 if over_type.type.fullname == 'builtins.dict':
1940 over_dict = True
1941 container_base_type = over_type
1942
1943 # now check base classes
1944 for base_type in over_type.type.bases:
1945 n = base_type.type.fullname
1946 if n == 'builtins.list':
1947 over_list = True
1948 container_base_type = base_type
1949 elif n == 'builtins.dict':
1950 over_dict = True
1951 container_base_type = base_type
1952
1953 assert not (over_dict and over_list)
1954
1955 if over_list:
1956 c_type = GetCType(over_type)
1957 assert c_type.endswith('*'), c_type
1958 inner_c_type = GetCType(container_base_type.args[0])
1959 c_iter_type = 'ListIter<%s>' % inner_c_type
1960
1961 # ReverseListIter!
1962 if reverse:
1963 c_iter_type = 'Reverse' + c_iter_type
1964
1965 elif over_dict:
1966 key_c_type = GetCType(container_base_type.args[0])
1967 val_c_type = GetCType(container_base_type.args[1])
1968 c_iter_type = 'DictIter<%s, %s>' % (key_c_type, val_c_type)
1969 assert not reverse
1970
1971 elif over_type.type.fullname == 'builtins.str':
1972 c_iter_type = 'StrIter'
1973 assert not reverse # can't reverse iterate over string yet
1974
1975 elif over_type.type.fullname == 'typing.Iterator':
1976 # We're iterating over a generator. Create a temporary List<T> on
1977 # the stack to accumulate the results in one big batch.
1978 c_iter_type = GetCType(over_type)
1979
1980 assert len(over_type.args) == 1, over_type.args
1981 inner_c_type = GetCType(over_type.args[0])
1982
1983 # eager_list_name is used below
1984 eager_list_name = 'YIELD_for_%d' % self.unique_id
1985 eager_list_type = 'List<%s>*' % inner_c_type
1986 self.unique_id += 1
1987
1988 self.write_ind('List<%s> %s;\n', inner_c_type, eager_list_name)
1989 self.write_ind('')
1990
1991 # ForStmt - could be self.generator_for_stmt
1992 #
1993 # for x in my_generator(42):
1994 # log('x = %s', x)
1995 #
1996 # Turns into
1997 # List<T> _for_yield_acc3;
1998 # my_generator(42, &_for_yield_acc3);
1999 # for (ListIter it(_for_yield_acc3) ...)
2000
2001 self.yield_eager_for[o] = (eager_list_name, eager_list_type)
2002
2003 self.yield_for_node = o # ForStmt
2004 self.accept(iterated_over)
2005 self.yield_for_node = None
2006
2007 self.write(';\n')
2008
2009 else: # assume it's like d.iteritems()? Iterator type
2010 assert False, over_type
2011
2012 if index0_name:
2013 # can't initialize two things in a for loop, so do it on a separate line
2014 self.write_ind('%s = 0;\n', index0_name)
2015 index_update = ', ++%s' % index0_name
2016 else:
2017 index_update = ''
2018
2019 self.write_ind('for (%s it(', c_iter_type)
2020 if eager_list_name:
2021 self.write('&%s', eager_list_name)
2022 else:
2023 self.accept(iterated_over) # the thing being iterated over
2024 self.write('); !it.Done(); it.Next()%s) {\n', index_update)
2025
2026 # for x in it: ...
2027 # for i, x in enumerate(pairs): ...
2028
2029 if isinstance(item_type, Instance) or index0_name:
2030 c_item_type = GetCType(item_type)
2031 self.write_ind(' %s ', c_item_type)
2032 self.accept(index_expr)
2033 if over_dict:
2034 self.write(' = it.Key();\n')
2035 else:
2036 self.write(' = it.Value();\n')
2037
2038 # Register loop variable as a stack root.
2039 # Note we have mylib.Collect() in CommandEvaluator::_Execute(), and
2040 # it's called in a loop by _ExecuteList(). Although the 'child'
2041 # variable is already live by other means.
2042 # TODO: Test how much this affects performance.
2043 if CTypeIsManaged(c_item_type) and not self.stack_roots:
2044 self.write_ind(' StackRoot _for(&')
2045 self.accept(index_expr)
2046 self.write_ind(');\n')
2047
2048 elif isinstance(item_type, TupleType): # for x, y in pairs
2049 if over_dict:
2050 assert isinstance(o.index, TupleExpr), o.index
2051 index_items = o.index.items
2052 assert len(index_items) == 2, index_items
2053 assert len(item_type.items) == 2, item_type.items
2054
2055 key_type = GetCType(item_type.items[0])
2056 val_type = GetCType(item_type.items[1])
2057
2058 #log('** %s key_type %s', item_type.items[0], key_type)
2059 #log('** %s val_type %s', item_type.items[1], val_type)
2060
2061 assert isinstance(index_items[0], NameExpr), index_items[0]
2062 assert isinstance(index_items[1], NameExpr), index_items[1]
2063
2064 # TODO(StackRoots): k, v
2065 self.write_ind(' %s %s = it.Key();\n', key_type,
2066 index_items[0].name)
2067 self.write_ind(' %s %s = it.Value();\n', val_type,
2068 index_items[1].name)
2069
2070 else:
2071 # Example:
2072 # for (ListIter it(mylist); !it.Done(); it.Next()) {
2073 # Tuple2<int, BigStr*> tup1 = it.Value();
2074 # int i = tup1->at0();
2075 # BigStr* s = tup1->at1();
2076 # log("%d %s", i, s);
2077 # }
2078
2079 c_item_type = GetCType(item_type)
2080
2081 if isinstance(o.index, TupleExpr):
2082 # TODO(StackRoots)
2083 temp_name = 'tup%d' % self.unique_id
2084 self.unique_id += 1
2085 self.write_ind(' %s %s = it.Value();\n', c_item_type,
2086 temp_name)
2087
2088 # loop - for x, y in other:
2089 self.indent += 1
2090 self._WriteTupleUnpackingInLoop(temp_name, o.index.items,
2091 item_type.items)
2092 self.indent -= 1
2093
2094 elif isinstance(o.index, NameExpr):
2095 self.write_ind(' %s %s = it.Value();\n', c_item_type,
2096 o.index.name)
2097 #self.write_ind(' StackRoots _for(&%s)\n;', o.index.name)
2098
2099 else:
2100 raise AssertionError()
2101
2102 else:
2103 raise AssertionError('Unexpected type %s' % item_type)
2104
2105 # Copy of visit_block, without opening {
2106 self.indent += 1
2107 block = o.body
2108 self._WriteBody(block.body)
2109 self.indent -= 1
2110 self.write_ind('}\n')
2111
2112 if o.else_body:
2113 raise AssertionError("can't translate for-else")
2114
2115 def _WriteCases(self, switch_expr: Expression, cases: util.CaseList,
2116 default_block: Union['mypy.nodes.Block', int]) -> None:
2117 """ Write a list of (expr, block) pairs """
2118
2119 for expr, body in cases:
2120 assert expr is not None, expr
2121 if not isinstance(expr, CallExpr):
2122 self.report_error(expr,
2123 'Expected call like case(x), got %s' % expr)
2124 return
2125
2126 for i, arg in enumerate(expr.args):
2127 if i != 0:
2128 self.write('\n')
2129 self.write_ind('case ')
2130 self.accept(arg)
2131 self.write(': ')
2132
2133 self.accept(body)
2134 self.write_ind(' break;\n')
2135
2136 if default_block == -1:
2137 # an error occurred
2138 return
2139 if default_block == -2:
2140 # This is too restrictive
2141 #self.report_error(switch_expr,
2142 # 'switch got no else: for default block')
2143 return
2144
2145 # Narrow the type
2146 assert not isinstance(default_block, int), default_block
2147
2148 self.write_ind('default: ')
2149 self.accept(default_block)
2150 # don't write 'break'
2151
2152 def _WriteSwitch(self, expr: CallExpr, o: 'mypy.nodes.WithStmt') -> None:
2153 """Write a switch statement over integers."""
2154 assert len(expr.args) == 1, expr.args
2155
2156 self.write_ind('switch (')
2157 self.accept(expr.args[0])
2158 self.write(') {\n')
2159
2160 assert len(o.body.body) == 1, o.body.body
2161 if_node = o.body.body[0]
2162 assert isinstance(if_node, IfStmt), if_node
2163
2164 self.indent += 1
2165 cases: util.CaseList = []
2166 default_block = util.CollectSwitchCases(self.module_path,
2167 if_node,
2168 cases,
2169 errors=self.errors_keep_going)
2170 self._WriteCases(expr, cases, default_block)
2171
2172 self.indent -= 1
2173 self.write_ind('}\n')
2174
2175 def _WriteTagSwitch(self, expr: CallExpr,
2176 o: 'mypy.nodes.WithStmt') -> None:
2177 """Write a switch statement over ASDL types."""
2178 assert len(expr.args) == 1, expr.args
2179
2180 self.write_ind('switch (')
2181 self.accept(expr.args[0])
2182 self.write('->tag()) {\n')
2183
2184 assert len(o.body.body) == 1, o.body.body
2185 if_node = o.body.body[0]
2186 assert isinstance(if_node, IfStmt), if_node
2187
2188 self.indent += 1
2189 cases: util.CaseList = []
2190 default_block = util.CollectSwitchCases(self.module_path,
2191 if_node,
2192 cases,
2193 errors=self.errors_keep_going)
2194 self._WriteCases(expr, cases, default_block)
2195
2196 self.indent -= 1
2197 self.write_ind('}\n')
2198
2199 def _StrSwitchCases(self, cases: util.CaseList) -> Any:
2200 cases2: List[Tuple[int, str, 'mypy.nodes.Block']] = []
2201 for expr, body in cases:
2202 if not isinstance(expr, CallExpr):
2203 # non-fatal check from CollectSwitchCases
2204 break
2205
2206 args = expr.args
2207 if len(args) != 1:
2208 self.report_error(
2209 expr,
2210 'str_switch can only have case("x"), not case("x", "y"): got %r'
2211 % args)
2212 break
2213
2214 if not isinstance(args[0], StrExpr):
2215 self.report_error(
2216 expr,
2217 'str_switch can only be used with constant strings, got %s'
2218 % args[0])
2219 break
2220
2221 s = args[0].value
2222 cases2.append((len(s), s, body))
2223
2224 # Sort by string length
2225 cases2.sort(key=lambda pair: pair[0])
2226 grouped = itertools.groupby(cases2, key=lambda pair: pair[0])
2227 return grouped
2228
2229 def _WriteStrSwitch(self, expr: CallExpr,
2230 o: 'mypy.nodes.WithStmt') -> None:
2231 """Write a switch statement over strings."""
2232 assert len(expr.args) == 1, expr.args
2233
2234 switch_expr = expr # for later error
2235
2236 switch_var = expr.args[0]
2237 if not isinstance(switch_var, NameExpr):
2238 self.report_error(
2239 expr.args[0],
2240 'str_switch(x) accepts only a variable name, got %s' %
2241 switch_var)
2242 return
2243
2244 self.write_ind('switch (len(%s)) {\n' % switch_var.name)
2245
2246 # There can only be one thing under 'with str_switch'
2247 assert len(o.body.body) == 1, o.body.body
2248 if_node = o.body.body[0]
2249 assert isinstance(if_node, IfStmt), if_node
2250
2251 self.indent += 1
2252
2253 cases: util.CaseList = []
2254 default_block = util.CollectSwitchCases(self.module_path,
2255 if_node,
2256 cases,
2257 errors=self.errors_keep_going)
2258
2259 grouped_cases = self._StrSwitchCases(cases)
2260 # Warning: this consumes internal iterator
2261 #self.log('grouped %s', list(grouped_cases))
2262
2263 for str_len, group in grouped_cases:
2264 self.write_ind('case %s: {\n' % str_len)
2265 if_num = 0
2266 for _, case_str, block in group:
2267 self.indent += 1
2268
2269 else_str = '' if if_num == 0 else 'else '
2270 self.write_ind('%sif (str_equals_c(%s, %s, %d)) ' %
2271 (else_str, switch_var.name,
2272 PythonStringLiteral(case_str), str_len))
2273 self.accept(block)
2274
2275 self.indent -= 1
2276 if_num += 1
2277
2278 self.indent += 1
2279 self.write_ind('else {\n')
2280 self.write_ind(' goto str_switch_default;\n')
2281 self.write_ind('}\n')
2282 self.indent -= 1
2283
2284 self.write_ind('}\n')
2285 self.write_ind(' break;\n')
2286
2287 if default_block == -1:
2288 # an error occurred
2289 return
2290 if default_block == -2:
2291 self.report_error(switch_expr,
2292 'str_switch got no else: for default block')
2293 return
2294
2295 # Narrow the type
2296 assert not isinstance(default_block, int), default_block
2297
2298 self.write('\n')
2299 self.write_ind('str_switch_default:\n')
2300 self.write_ind('default: ')
2301 self.accept(default_block)
2302
2303 self.indent -= 1
2304 self.write_ind('}\n')
2305
2306 def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> None:
2307 """
2308 Translate only blocks of this form:
2309
2310 with switch(x) as case:
2311 if case(0):
2312 print('zero')
2313 elif case(1, 2, 3):
2314 print('low')
2315 else:
2316 print('other')
2317
2318 switch(x) {
2319 case 0:
2320 print('zero')
2321 break;
2322 case 1:
2323 case 2:
2324 case 3:
2325 print('low')
2326 break;
2327 default:
2328 print('other')
2329 break;
2330 }
2331
2332 Or:
2333
2334 with ctx_Bar(bar, x, y):
2335 x()
2336
2337 {
2338 ctx_Bar(bar, x, y)
2339 x();
2340 }
2341 """
2342 #log('WITH')
2343 #log('expr %s', o.expr)
2344 #log('target %s', o.target)
2345
2346 assert len(o.expr) == 1, o.expr
2347 expr = o.expr[0]
2348 assert isinstance(expr, CallExpr), expr
2349
2350 # There is no 'with mylib.tagswitch(x)', only 'with tagswitch(x)'
2351 # But we have with alloc.ctx_SourceCode
2352 #assert isinstance(expr.callee, NameExpr), expr.callee
2353
2354 callee_name = expr.callee.name
2355 if callee_name == 'switch':
2356 self._WriteSwitch(expr, o)
2357 elif callee_name == 'str_switch':
2358 self._WriteStrSwitch(expr, o)
2359 elif callee_name == 'tagswitch':
2360 self._WriteTagSwitch(expr, o)
2361 else:
2362 assert isinstance(expr, CallExpr), expr
2363 self.write_ind('{ // with\n')
2364 self.indent += 1
2365
2366 self.write_ind('')
2367 self.accept(expr.callee)
2368
2369 # FIX: Use braced initialization to avoid most-vexing parse when
2370 # there are 0 args!
2371 self.write(' ctx{')
2372 for i, arg in enumerate(expr.args):
2373 if i != 0:
2374 self.write(', ')
2375 self.accept(arg)
2376 self.write('};\n\n')
2377
2378 self._WriteBody(o.body.body)
2379
2380 self.indent -= 1
2381 self.write_ind('}\n')
2382
2383 def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> None:
2384
2385 d = o.expr
2386 if isinstance(d, IndexExpr):
2387 self.write_ind('')
2388 self.accept(d.base)
2389
2390 if isinstance(d.index, SliceExpr):
2391 # del mylist[:] -> mylist->clear()
2392
2393 sl = d.index
2394 assert sl.begin_index is None, sl
2395 assert sl.end_index is None, sl
2396 self.write('->clear()')
2397 else:
2398 # del mydict[mykey] raises KeyError, which we don't want
2399 raise AssertionError(
2400 'Use mylib.dict_erase(d, key) instead of del d[key]')
2401
2402 self.write(';\n')
2403
2404 def oils_visit_constructor(self, o: ClassDef, stmt: FuncDef,
2405 base_class_sym: util.SymbolPath) -> None:
2406 self.write('\n')
2407 self.write('%s::%s(', o.name, o.name)
2408 self._WriteFuncParams(stmt, write_defaults=False)
2409 self.write(')')
2410
2411 first_index = 0
2412
2413 # Skip docstring
2414 maybe_skip_stmt = stmt.body.body[0]
2415 if (isinstance(maybe_skip_stmt, ExpressionStmt) and
2416 isinstance(maybe_skip_stmt.expr, StrExpr)):
2417 first_index += 1
2418
2419 # Check for Base.__init__(self, ...) and move that to the initializer list.
2420 first_stmt = stmt.body.body[first_index]
2421 if (isinstance(first_stmt, ExpressionStmt) and
2422 isinstance(first_stmt.expr, CallExpr)):
2423 expr = first_stmt.expr
2424 #log('expr %s', expr)
2425 callee = first_stmt.expr.callee
2426
2427 # TextOutput() : ColorOutput(f), ... {
2428 if (isinstance(callee, MemberExpr) and callee.name == '__init__'):
2429 base_constructor_args = expr.args
2430 #log('ARGS %s', base_constructor_args)
2431 self.write(' : %s(',
2432 SymbolToString(base_class_sym, strip_package=True))
2433 for i, arg in enumerate(base_constructor_args):
2434 if i == 0:
2435 continue # Skip 'this'
2436 if i != 1:
2437 self.write(', ')
2438 self.accept(arg)
2439 self.write(')')
2440
2441 first_index += 1
2442
2443 self.write(' {\n')
2444
2445 # Now visit the rest of the statements
2446 self.indent += 1
2447
2448 if _IsContextManager(self.current_class_name):
2449 # For ctx_* classes only, do gHeap.PushRoot() for all the pointer
2450 # members
2451 member_vars = self.all_member_vars[o]
2452 for name in sorted(member_vars):
2453 _, c_type, is_managed = member_vars[name]
2454 if is_managed:
2455 # VALIDATE_ROOTS doesn't complain even if it's not
2456 # initialized? Should be initialized after PushRoot().
2457 #self.write_ind('this->%s = nullptr;\n' % name)
2458 self.write_ind(
2459 'gHeap.PushRoot(reinterpret_cast<RawObject**>(&(this->%s)));\n'
2460 % name)
2461
2462 for node in stmt.body.body[first_index:]:
2463 self.accept(node)
2464 self.indent -= 1
2465 self.write('}\n')
2466
2467 def oils_visit_dunder_exit(self, o: ClassDef, stmt: FuncDef,
2468 base_class_sym: util.SymbolPath) -> None:
2469 self.write('\n')
2470 self.write_ind('%s::~%s()', o.name, o.name)
2471
2472 self.write(' {\n')
2473 self.indent += 1
2474
2475 # TODO:
2476 # - Can't throw exception in destructor.
2477 # - Check that you don't return early from destructor. If so, we skip
2478 # PopRoot(), which messes up the invariant!
2479
2480 for node in stmt.body.body:
2481 self.accept(node)
2482
2483 # For ctx_* classes only , gHeap.PopRoot() for all the pointer members
2484 if _IsContextManager(self.current_class_name):
2485 member_vars = self.all_member_vars[o]
2486 for name in sorted(member_vars):
2487 _, c_type, is_managed = member_vars[name]
2488 if is_managed:
2489 self.write_ind('gHeap.PopRoot();\n')
2490 else:
2491 self.report_error(
2492 o, 'Any class with __exit__ should be named ctx_Foo (%s)' %
2493 (self.current_class_name, ))
2494 return
2495
2496 self.indent -= 1
2497 self.write('}\n')
2498
2499 def oils_visit_method(self, o: ClassDef, stmt: FuncDef,
2500 base_class_sym: util.SymbolPath) -> None:
2501 self.accept(stmt)
2502
2503 # Module structure
2504
2505 def visit_import(self, o: 'mypy.nodes.Import') -> None:
2506 pass
2507
2508 def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> None:
2509 """
2510 Write C++ namespace aliases and 'using' for imports.
2511 We need them in the 'decl' phase for default arguments like
2512 runtime_asdl::scope_e -> scope_e
2513 """
2514 if o.id in ('__future__', 'typing'):
2515 return # do nothing
2516
2517 for name, alias in o.names:
2518 #self.log('ImportFrom id: %s name: %s alias: %s', o.id, name, alias)
2519
2520 if name == 'log': # varargs translation
2521 continue
2522
2523 if o.id == 'mycpp.mylib':
2524 # These mylib functions are translated in a special way
2525 if name in ('switch', 'tagswitch', 'str_switch', 'iteritems',
2526 'NewDict', 'probe'):
2527 continue
2528 # STDIN_FILENO is #included
2529 if name == 'STDIN_FILENO':
2530 continue
2531
2532 # A heuristic that works for the Oils import style.
2533 if '.' in o.id:
2534 # from mycpp.mylib import log => using mylib::log
2535 translate_import = True
2536 else:
2537 # from core import util => NOT translated
2538 # We just rely on 'util' being defined.
2539 translate_import = False
2540
2541 if translate_import:
2542 dotted_parts = o.id.split('.')
2543 last_dotted = dotted_parts[-1]
2544
2545 # Omit these:
2546 # from _gen.ysh import grammar_nt
2547 if last_dotted == 'ysh':
2548 return
2549 # from _devbuild.gen import syntax_asdl
2550 if last_dotted == 'gen':
2551 return
2552
2553 # Problem:
2554 # - The decl stage has to return yaks_asdl::mod_def, so imports should go there
2555 # - But if you change this to decl_write() instead of
2556 # write(), you end up 'using error::e_usage' in say
2557 # 'assign_osh', and it hasn't been defined yet.
2558
2559 if alias:
2560 # using runtime_asdl::emit_e = EMIT;
2561 self.write_ind('using %s = %s::%s;\n', alias, last_dotted,
2562 name)
2563 else:
2564 # from _devbuild.gen.id_kind_asdl import Id
2565 # -> using id_kind_asdl::Id.
2566 using_str = 'using %s::%s;\n' % (last_dotted, name)
2567 self.write_ind(using_str)
2568
2569 # Fully qualified:
2570 # self.write_ind('using %s::%s;\n', '::'.join(dotted_parts), name)
2571
2572 else:
2573 # If we're importing a module without an alias, we don't need to do
2574 # anything. 'namespace cmd_eval' is already defined.
2575 if not alias:
2576 return
2577
2578 # from asdl import format as fmt
2579 # -> namespace fmt = format;
2580 self.write_ind('namespace %s = %s;\n', alias, name)
2581
2582 # Statements
2583
2584 def _WriteLocals(self, local_var_list: List[LocalVar]) -> None:
2585 # TODO: put the pointers first, and then register a single
2586 # StackRoots record.
2587 done = set()
2588 for lval_name, lval_type, is_param in local_var_list:
2589 c_type = GetCType(lval_type)
2590 if not is_param and lval_name not in done:
2591 if util.SMALL_STR and c_type == 'Str':
2592 self.write_ind('%s %s(nullptr);\n', c_type, lval_name)
2593 else:
2594 rhs = ' = nullptr' if CTypeIsManaged(c_type) else ''
2595 self.write_ind('%s %s%s;\n', c_type, lval_name, rhs)
2596
2597 # TODO: we're not skipping the assignment, because of
2598 # the RHS
2599 if util.IsUnusedVar(lval_name):
2600 # suppress C++ unused var compiler warnings!
2601 self.write_ind('(void)%s;\n' % lval_name)
2602
2603 done.add(lval_name)
2604
2605 # Figure out if we have any roots to write with StackRoots
2606 roots = [] # keep it sorted
2607 full_func_name = None
2608 if self.current_func_node:
2609 full_func_name = SplitPyName(self.current_func_node.fullname)
2610
2611 for lval_name, lval_type, is_param in local_var_list:
2612 c_type = GetCType(lval_type)
2613 #self.log('%s %s %s', lval_name, c_type, is_param)
2614 if lval_name not in roots and CTypeIsManaged(c_type):
2615 if (not self.stack_roots or self.stack_roots.needs_root(
2616 full_func_name, SplitPyName(lval_name))):
2617 roots.append(lval_name)
2618
2619 #self.log('roots %s', roots)
2620
2621 if len(roots):
2622 if (self.stack_roots_warn and len(roots) > self.stack_roots_warn):
2623 log('WARNING: %s() has %d stack roots. Consider refactoring this function.'
2624 % (self.current_func_node.fullname, len(roots)))
2625
2626 for i, r in enumerate(roots):
2627 self.write_ind('StackRoot _root%d(&%s);\n' % (i, r))
2628
2629 self.write('\n')
2630
2631 def visit_block(self, block: 'mypy.nodes.Block') -> None:
2632 self.write('{\n') # not indented to use same line as while/if
2633
2634 self.indent += 1
2635 self._WriteBody(block.body)
2636 self.indent -= 1
2637
2638 self.write_ind('}\n')
2639
2640 def oils_visit_expression_stmt(self,
2641 o: 'mypy.nodes.ExpressionStmt') -> None:
2642 self.write_ind('')
2643 self.accept(o.expr)
2644 self.write(';\n')
2645
2646 def visit_operator_assignment_stmt(
2647 self, o: 'mypy.nodes.OperatorAssignmentStmt') -> None:
2648 self.write_ind('')
2649 self.accept(o.lvalue)
2650 self.write(' %s= ', o.op) # + to +=
2651 self.accept(o.rvalue)
2652 self.write(';\n')
2653
2654 def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> None:
2655 self.write_ind('while (')
2656 self.accept(o.expr)
2657 self.write(') ')
2658 self.accept(o.body)
2659
2660 def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> None:
2661 # Examples:
2662 # return
2663 # return None
2664 # return my_int + 3;
2665 self.write_ind('return ')
2666 if o.expr:
2667 if not (isinstance(o.expr, NameExpr) and o.expr.name == 'None'):
2668
2669 # Note: the type of the return expression (self.types[o.expr])
2670 # and the return type of the FUNCTION are different. Use the
2671 # latter.
2672 ret_type = self.current_func_node.type.ret_type
2673
2674 c_ret_type, returning_tuple, _ = GetCReturnType(ret_type)
2675
2676 # return '', None # tuple literal
2677 # but NOT
2678 # return tuple_func()
2679 if returning_tuple and isinstance(o.expr, TupleExpr):
2680 self.write('%s(' % c_ret_type)
2681 for i, item in enumerate(o.expr.items):
2682 if i != 0:
2683 self.write(', ')
2684 self.accept(item)
2685 self.write(');\n')
2686 return
2687
2688 # Not returning tuple
2689 self.accept(o.expr)
2690
2691 self.write(';\n')
2692
2693 def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> None:
2694 # Not sure why this wouldn't be true
2695 assert len(o.expr) == 1, o.expr
2696
2697 condition = o.expr[0]
2698
2699 if not _CheckCondition(condition, self.types):
2700 self.report_error(
2701 o,
2702 "Use explicit len(obj) or 'obj is not None' for mystr, mylist, mydict"
2703 )
2704 return
2705
2706 if util.ShouldVisitIfExpr(o):
2707 self.write_ind('if (')
2708 for e in o.expr:
2709 self.accept(e)
2710 self.write(') ')
2711
2712 if util.ShouldVisitIfBody(o):
2713 cond = util.GetSpecialIfCondition(o)
2714 if cond == 'CPP':
2715 self.write_ind('// if MYCPP\n')
2716 self.write_ind('')
2717
2718 for body in o.body:
2719 self.accept(body)
2720
2721 if cond == 'CPP':
2722 self.write_ind('// endif MYCPP\n')
2723
2724 if util.ShouldVisitElseBody(o):
2725 cond = util.GetSpecialIfCondition(o)
2726 if cond == 'PYTHON':
2727 self.write_ind('// if not PYTHON\n')
2728 self.write_ind('')
2729
2730 if util.ShouldVisitIfBody(o):
2731 self.write_ind('else ')
2732
2733 self.accept(o.else_body)
2734
2735 if cond == 'PYTHON':
2736 self.write_ind('// endif MYCPP\n')
2737
2738 def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> None:
2739 self.write_ind('break;\n')
2740
2741 def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> None:
2742 self.write_ind('continue;\n')
2743
2744 def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> None:
2745 self.write_ind('; // pass\n')
2746
2747 def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> None:
2748 to_raise = o.expr
2749
2750 if to_raise:
2751 if isinstance(to_raise, CallExpr) and isinstance(
2752 to_raise.callee, NameExpr):
2753 callee_name = to_raise.callee.name
2754 if callee_name == 'AssertionError':
2755 # C++ compiler is aware of assert(0) for unreachable code
2756 self.write_ind('assert(0); // AssertionError\n')
2757 return
2758 if callee_name == 'NotImplementedError':
2759 self.write_ind(
2760 'FAIL(kNotImplemented); // Python NotImplementedError\n'
2761 )
2762 return
2763 self.write_ind('throw ')
2764 self.accept(to_raise)
2765 self.write(';\n')
2766 else:
2767 # raise without arg
2768 self.write_ind('throw;\n')
2769
2770 def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> None:
2771 self.write_ind('try ')
2772 self.accept(o.body)
2773 caught = False
2774
2775 for t, v, handler in zip(o.types, o.vars, o.handlers):
2776 c_type = None
2777
2778 if isinstance(t, NameExpr):
2779 if t.name in ('IOError', 'OSError'):
2780 self.report_error(
2781 handler,
2782 'Use except (IOError, OSError) rather than catching just one'
2783 )
2784 c_type = '%s*' % t.name
2785
2786 elif isinstance(t, MemberExpr):
2787 # We never use 'except foo.bar.T', only `foo.T'
2788 assert isinstance(t.expr, NameExpr), t.expr
2789 c_type = '%s::%s*' % (t.expr.name, t.name)
2790
2791 elif isinstance(t, TupleExpr):
2792 if len(t.items) == 2:
2793 e1 = t.items[0]
2794 e2 = t.items[1]
2795 if isinstance(e1, NameExpr) and isinstance(e2, NameExpr):
2796 names = [e1.name, e2.name]
2797 names.sort()
2798 if names == ['IOError', 'OSError']:
2799 c_type = 'IOError_OSError*' # Base class in mylib
2800
2801 else:
2802 raise AssertionError()
2803
2804 if c_type is None:
2805 self.report_error(o, "try couldn't determine c_type")
2806 return
2807
2808 if v:
2809 self.write_ind('catch (%s %s) ', c_type, v.name)
2810 else:
2811 self.write_ind('catch (%s) ', c_type)
2812 self.accept(handler)
2813
2814 caught = True
2815
2816 if not caught:
2817 self.report_error(o, 'try should have an except')
2818
2819 if o.else_body:
2820 self.report_error(o, 'try/else not supported')
2821
2822 if o.finally_body:
2823 self.report_error(o, 'try/finally not supported')