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

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