""" cppgen.py - AST pass to that prints C++ code """ import itertools import json # for "C escaping" from typing import overload, Union, Optional, Dict import mypy from mypy.visitor import ExpressionVisitor, StatementVisitor from mypy.types import (Type, AnyType, NoneTyp, TupleType, Instance, NoneType, Overloaded, CallableType, UnionType, UninhabitedType, PartialType, TypeAliasType) from mypy.nodes import (Expression, Statement, NameExpr, IndexExpr, MemberExpr, TupleExpr, ExpressionStmt, IfStmt, StrExpr, SliceExpr, FuncDef, UnaryExpr, OpExpr, ComparisonExpr, CallExpr, IntExpr, ListExpr, DictExpr, ListComprehension) from mycpp import format_strings from mycpp.crash import catch_errors from mycpp.util import log, join_name, split_py_name, IsStr from mycpp import pass_state from mycpp import util from typing import Tuple, List T = None NAME_CONFLICTS = ('stdin', 'stdout', 'stderr') class UnsupportedException(Exception): pass def _IsContextManager(class_name): return class_name[-1].startswith('ctx_') def _SkipAssignment(var_name): """ Skip at the top level: _ = log unused1 = log Always skip: x, _ = mytuple # no second var """ return var_name == '_' or var_name.startswith('unused') def _GetCTypeForCast(type_expr): """ MyPy cast() """ if isinstance(type_expr, MemberExpr): subtype_name = '%s::%s' % (type_expr.expr.name, type_expr.name) elif isinstance(type_expr, IndexExpr): # List[word_t] would be a problem. # But worked around it in osh/word_parse.py #subtype_name = 'List' raise AssertionError() elif isinstance(type_expr, StrExpr): parts = type_expr.value.split('.') subtype_name = '::'.join(parts) else: subtype_name = type_expr.name # Hack for now if subtype_name != 'int' and subtype_name != 'mops::BigInt': subtype_name += '*' return subtype_name def _GetCastKind(module_path, cast_to_type): """Translate MyPy cast to C++ cast. Prefer static_cast, but sometimes we need reinterpret_cast. """ cast_kind = 'static_cast' # Hack for Id.Expr_CastedDummy in expr_to_ast.py if 'expr_to_ast.py' in module_path: for name in ( 'ShArrayLiteral', 'CommandSub', 'BracedVarSub', 'DoubleQuoted', 'SingleQuoted', # Another kind of hack, not because of CastDummy 'y_lhs_t', ): if name in cast_to_type: cast_kind = 'reinterpret_cast' break # The other side of Id.Expr_CastedDummy if 'expr_parse.py' in module_path: for name in ('Token', ): if name in cast_to_type: cast_kind = 'reinterpret_cast' break if 'process.py' in module_path and 'mylib::Writer' in cast_to_type: cast_kind = 'reinterpret_cast' return cast_kind def _GetContainsFunc(t): """ x in y """ contains_func = None if isinstance(t, Instance): type_name = t.type.fullname if type_name == 'builtins.list': contains_func = 'list_contains' elif type_name == 'builtins.str': contains_func = 'str_contains' elif type_name == 'builtins.dict': contains_func = 'dict_contains' elif isinstance(t, UnionType): # Special case for Optional[T] == Union[T, None] if len(t.items) != 2: raise NotImplementedError('Expected Optional, got %s' % t) if not isinstance(t.items[1], NoneTyp): raise NotImplementedError('Expected Optional, got %s' % t) contains_func = _GetContainsFunc(t.items[0]) return contains_func # None checked later def _EqualsFunc(left_type): if IsStr(left_type): return 'str_equals' if (isinstance(left_type, UnionType) and len(left_type.items) == 2 and IsStr(left_type.items[0]) and isinstance(left_type.items[1], NoneTyp)): return 'maybe_str_equals' return None _EXPLICIT = ('builtins.str', 'builtins.list', 'builtins.dict') def _CheckCondition(node, types): """ Ban if (mystr) if (mylist) if (mydict) They mean non-empty in Python. """ #log('NODE %s', node) if isinstance(node, UnaryExpr) and node.op == 'not': return _CheckCondition(node.expr, types) if isinstance(node, OpExpr): #log('OpExpr node %s %s', node, dir(node)) # if x > 0 and not mylist, etc. return _CheckCondition(node.left, types) and _CheckCondition( node.right, types) t = types[node] if isinstance(t, Instance): type_name = t.type.fullname if type_name in _EXPLICIT: return False elif isinstance(t, UnionType): if len(t.items) == 2 and isinstance(t.items[1], NoneTyp): t2 = t.items[0] if t2.type.fullname in _EXPLICIT: return False return True def CTypeIsManaged(c_type): # type: (str) -> bool """For rooting and field masks.""" assert c_type != 'void' if util.SMALL_STR: if c_type == 'Str': return True # int, double, bool, scope_t enums, etc. are not managed return c_type.endswith('*') def GetCType(t, param=False, local=False): """Recursively translate MyPy type to C++ type.""" is_pointer = False if isinstance(t, NoneTyp): # e.g. a function that doesn't return anything return 'void' elif isinstance(t, AnyType): # Note: this usually results in another compile-time error. We should get # rid of the 'Any' types. c_type = 'void' is_pointer = True elif isinstance(t, PartialType): # Note: bin/oil.py has some of these? Not sure why. c_type = 'void' is_pointer = True # TODO: It seems better not to check for string equality, but that's what # mypyc/genops.py does? elif isinstance(t, Instance): type_name = t.type.fullname #log('** TYPE NAME %s', type_name) if type_name == 'builtins.int': c_type = 'int' elif type_name == 'builtins.float': c_type = 'double' elif type_name == 'builtins.bool': c_type = 'bool' elif type_name == 'builtins.str': if util.SMALL_STR: c_type = 'Str' is_pointer = False else: c_type = 'BigStr' is_pointer = True elif type_name == 'builtins.list': assert len(t.args) == 1, t.args type_param = t.args[0] inner_c_type = GetCType(type_param) c_type = 'List<%s>' % inner_c_type is_pointer = True elif type_name == 'builtins.dict': params = [] for type_param in t.args: params.append(GetCType(type_param)) c_type = 'Dict<%s>' % ', '.join(params) is_pointer = True elif 'BigInt' in type_name: # also spelled mycpp.mylib.BigInt c_type = 'mops::BigInt' # Not a pointer! elif type_name == 'typing.IO': c_type = 'mylib::File' is_pointer = True elif type_name == 'typing.Iterator': assert len(t.args) == 1, t.args type_param = t.args[0] inner_c_type = GetCType(type_param) c_type = 'ListIter<%s>' % inner_c_type else: # note: fullname => 'parse.Lexer'; name => 'Lexer' base_class_names = [b.type.fullname for b in t.type.bases] #log('** base_class_names %s', base_class_names) # Check base class for pybase.SimpleObj so we can output # expr_asdl::tok_t instead of expr_asdl::tok_t*. That is a enum, while # expr_t is a "regular base class". # NOTE: Could we avoid the typedef? If it's SimpleObj, just generate # tok_e instead? if 'asdl.pybase.SimpleObj' not in base_class_names: is_pointer = True parts = t.type.fullname.split('.') c_type = '%s::%s' % (parts[-2], parts[-1]) elif isinstance(t, UninhabitedType): # UninhabitedType has a NoReturn flag c_type = 'void' elif isinstance(t, TupleType): inner_c_types = [] for inner_type in t.items: c_type = GetCType(inner_type) if c_type == 'void': # Why does MyPy give us 'None' instead of type declared with type: ? # log('**** items %s', t.items) pass inner_c_types.append(c_type) c_type = 'Tuple%d<%s>' % (len(t.items), ', '.join(inner_c_types)) is_pointer = True elif isinstance(t, UnionType): # Special case for Optional[IOError_OSError] # == Union[IOError, OSError, None] num_items = len(t.items) if num_items == 3: t0 = t.items[0] t1 = t.items[1] t2 = t.items[2] t0_name = t0.type.fullname t1_name = t1.type.fullname if t0_name != 'builtins.IOError': raise NotImplementedError( 'Expected Union[IOError, OSError, None]: t0 = %s' % t0_name) if t1_name != 'builtins.OSError': raise NotImplementedError( 'Expected Union[IOError, OSError, None]: t1 = %s' % t1_name) if not isinstance(t2, NoneTyp): raise NotImplementedError( 'Expected Union[IOError, OSError, None]') c_type = 'IOError_OSError' is_pointer = True elif num_items == 2: t0 = t.items[0] t1 = t.items[1] c_type = None if isinstance(t1, NoneTyp): # Optional[T0] c_type = GetCType(t.items[0]) else: # Detect type alias defined in core/error.py # IOError_OSError = Union[IOError, OSError] t0_name = t0.type.fullname t1_name = t1.type.fullname if (t0_name == 'builtins.IOError' and t1_name == 'builtins.OSError'): c_type = 'IOError_OSError' is_pointer = True if c_type is None: raise NotImplementedError('Unexpected Union type %s' % t) else: raise NotImplementedError( 'Expected 2 or 3 items in Union, got %s' % num_items) elif isinstance(t, CallableType): # Function types are expanded # Callable[[Parser, Token, int], arith_expr_t] # -> arith_expr_t* (*f)(Parser*, Token*, int) nud; ret_type = GetCType(t.ret_type) arg_types = [GetCType(typ) for typ in t.arg_types] c_type = '%s (*f)(%s)' % (ret_type, ', '.join(arg_types)) elif isinstance(t, TypeAliasType): if 0: log('***') log('%s', t) log('%s', dir(t)) log('%s', t.alias) log('%s', dir(t.alias)) log('%s', t.alias.target) log('***') return GetCType(t.alias.target) else: raise NotImplementedError('MyPy type: %s %s' % (type(t), t)) if is_pointer: if param or local: c_type = 'Local<%s>' % c_type else: c_type += '*' return c_type def GetCReturnType(t) -> Tuple[str, bool, Optional[str]]: """ Returns a C string, whether the tuple-by-value optimization was applied, and the C type of an extra output param if the function is a generator. """ c_ret_type = GetCType(t) # Optimization: Return tupels BY VALUE if isinstance(t, TupleType): assert c_ret_type.endswith('*') return c_ret_type[:-1], True, None elif c_ret_type.startswith('ListIter<'): assert len(t.args) == 1, t.args inner_c_type = GetCType(t.args[0]) return 'void', False, 'List<%s>*' % inner_c_type else: return c_ret_type, False, None def PythonStringLiteral(s: str) -> str: """ Returns a properly quoted string. """ # MyPy does bad escaping. Decode and push through json to get something # workable in C++. return json.dumps(format_strings.DecodeMyPyString(s)) class Generate(ExpressionVisitor[T], StatementVisitor[None]): def __init__(self, types: Dict[Expression, Type], const_lookup, f, virtual=None, local_vars=None, ctx_member_vars=None, decl=False, forward_decl=False, stack_roots_warn=None, dot_exprs=None): self.types = types self.const_lookup = const_lookup self.f = f self.virtual = virtual # We collect local_vars and ctx-member_vars in the DECL phase, and # write them in the IMPL phase. self.local_vars = local_vars # Dict[FuncDef node, list of type, var] self.ctx_member_vars = ctx_member_vars # for rooting self.decl = decl self.forward_decl = forward_decl self.stack_roots_warn = stack_roots_warn self.unique_id = 0 self.indent = 0 self.local_var_list = [] # Collected at assignment self.prepend_to_block = None # For writing vars after { # Temporary lists to use as output params for generators self.yield_accumulators = { } # type: Dict[Union[Statement, FuncDef], Tuple[str, str]] self.current_func_node = None self.current_stmt_node = None # DECL pass: self.current_member_vars holds the vars for the current # class. It's cleared when we start looking at a class. Then we visit # all the methods, and accumulate the types of everything that looks # like self.foo = 1. Then we write C++ class member declarations at # the end of the class. self.current_member_vars: Dict[str, Type] = {} self.current_class_name = None # for prototypes self.current_method_name = None self.dot_exprs = dot_exprs # So we can report multiple at once # module path, line number, message self.errors_keep_going: List[Tuple[str, int, str]] = [] self.writing_default_arg = False def log(self, msg, *args): ind_str = self.indent * ' ' log(ind_str + msg, *args) def always_write(self, msg, *args): """Write unconditionally - forward decl, decl, def """ if args: msg = msg % args self.f.write(msg) def always_write_ind(self, msg, *args): ind_str = self.indent * ' ' self.always_write(ind_str + msg, *args) def def_write(self, msg, *args): """Write only in definitions.""" if self.forward_decl: return if self.decl and not self.writing_default_arg: return if args: msg = msg % args self.f.write(msg) def def_write_ind(self, msg, *args): ind_str = self.indent * ' ' self.def_write(ind_str + msg, *args) def decl_write(self, msg, *args): """Write only in the decl stage (not forward declarations)""" if not self.decl: return if args: msg = msg % args self.f.write(msg) def decl_write_ind(self, msg, *args): ind_str = self.indent * ' ' self.decl_write(ind_str + msg, *args) # # COPIED from IRBuilder # @overload def accept(self, node: Expression) -> T: ... @overload def accept(self, node: Statement) -> None: ... def accept(self, node: Union[Statement, Expression]) -> Optional[T]: with catch_errors(self.module_path, node.line): if isinstance(node, Expression): try: res = node.accept(self) #res = self.coerce(res, self.node_type(node), node.line) # If we hit an error during compilation, we want to # keep trying, so we can produce more error # messages. Generate a temp of the right type to keep # from causing more downstream trouble. except UnsupportedException: res = self.alloc_temp(self.node_type(node)) return res else: try: node.accept(self) except UnsupportedException: pass return None def report_error(self, node: Union[Statement, Expression], msg: str): err = (self.module_path, node.line, msg) self.errors_keep_going.append(err) def not_translated(self, node: Union[Statement, Expression], name: str): self.report_error(node, '%s not translated' % name) def not_python2(self, node: Union[Statement, Expression], name: str): self.report_error(node, "%s: shouldn't get here in Python 2" % name) # Not in superclasses: def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> T: if util.ShouldSkipPyFile(o): return #self.log('') #self.log('mypyfile %s', o.fullname) mod_parts = o.fullname.split('.') if self.forward_decl: comment = 'forward declare' elif self.decl: comment = 'declare' else: comment = 'define' self.always_write_ind('namespace %s { // %s\n', mod_parts[-1], comment) self.always_write('\n') self.module_path = o.path if self.forward_decl: self.indent += 1 #self.log('defs %s', o.defs) for node in o.defs: # skip module docstring if (isinstance(node, ExpressionStmt) and isinstance(node.expr, StrExpr)): continue self.accept(node) if self.forward_decl: self.indent -= 1 self.always_write('\n') self.always_write_ind('} // %s namespace %s\n', comment, mod_parts[-1]) self.always_write('\n') for path, line_num, msg in self.errors_keep_going: self.log('%s:%s %s', path, line_num, msg) # NOTE: Copied ExpressionVisitor and StatementVisitor nodes below! # LITERALS def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> T: self.def_write(str(o.value)) def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: # e.g. for arg.t > 0.0 self.def_write(str(o.value)) def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> T: self.def_write(self.const_lookup[o]) # UNHANDLED def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> T: self.not_python2(o, 'bytes expr') def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> T: self.not_translated(o, 'unicode expr') def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: self.not_translated(o, 'complex expr') def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> T: # is this in .pyi files only? self.not_translated(o, 'ellipsis') # Expressions def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> T: # mycpp/examples/invalid_python.py doesn't hit this? self.not_translated(o, 'star expr') def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> T: if o.name == 'None': self.def_write('nullptr') return if o.name == 'True': self.def_write('true') return if o.name == 'False': self.def_write('false') return if o.name == 'self': self.def_write('this') return if o.name in NAME_CONFLICTS: self.report_error( o, "The name %r conflicts with C macros on some platforms; choose a different name" % o.name) return self.def_write(o.name) def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> T: if o.expr: dot_expr = self.dot_exprs[o] if isinstance(dot_expr, pass_state.StackObjectMember): op = '.' elif isinstance(dot_expr, pass_state.StaticObjectMember) or isinstance(dot_expr, pass_state.ModuleMember): op = '::' elif isinstance(dot_expr, pass_state.HeapObjectMember): op = '->' else: assert False, o self.accept(o.expr) self.def_write(op) if o.name == 'errno': # e->errno -> e->errno_ to avoid conflict with C macro self.def_write('errno_') elif o.name in NAME_CONFLICTS: self.report_error( o, "The name %r conflicts with C macros on some platforms; choose a different name" % o.name) else: self.def_write('%s', o.name) def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> T: self.not_python2(o, 'yield from') def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> T: assert self.current_func_node in self.yield_accumulators self.def_write_ind('%s->append(', self.yield_accumulators[self.current_func_node][0]) self.accept(o.expr) self.def_write(');\n') def _WriteArgList(self, o): self.def_write('(') for i, arg in enumerate(o.args): if i != 0: self.def_write(', ') self.accept(arg) # Will be set if we're: # a) accumulating the output of an iterator # b) constructing an iterator with the result of (a) if self.current_stmt_node in self.yield_accumulators: if len(o.args) > 0: self.def_write(', ') arg_name, _ = self.yield_accumulators[self.current_stmt_node] self.def_write('&%s', arg_name) self.def_write(')') def _IsInstantiation(self, o): callee_name = o.callee.name callee_type = self.types[o.callee] # e.g. int() takes str, float, etc. It doesn't matter for translation. if isinstance(callee_type, Overloaded): if 0: for item in callee_type.items(): self.log('item: %s', item) if isinstance(callee_type, CallableType): # If the function name is the same as the return type, then add # 'Alloc<>'. f = Foo() => f = Alloc(). ret_type = callee_type.ret_type # str(i) doesn't need new. For now it's a free function. # TODO: rename int_to_str? or BigStr::from_int()? if (callee_name not in ('str', 'bool', 'float') and 'BigInt' not in callee_name and isinstance(ret_type, Instance)): ret_type_name = ret_type.type.name # HACK: Const is the callee; expr.Const is the return type if (ret_type_name == callee_name or ret_type_name.endswith('__' + callee_name)): return True return False def _ProbeExpr(self, o): assert len(o.args) >= 2 and len(o.args) < 13, o.args assert isinstance(o.args[0], mypy.nodes.StrExpr), o.args[0] assert isinstance(o.args[1], mypy.nodes.StrExpr), o.args[1] arity = len(o.args) - 2 macro = 'DTRACE_PROBE' if arity > 0: macro = 'DTRACE_PROBE%d' % arity self.def_write('%s(%s, %s', macro, o.args[0].value, o.args[1].value) for arg in o.args[2:]: arg_type = self.types[arg] self.def_write(', ') if (isinstance(arg_type, Instance) and arg_type.type.fullname == 'builtins.str'): self.def_write('%s->data()' % arg.name) else: self.accept(arg) self.def_write(')') def _LogExpr(self, o): args = o.args if len(args) == 1: # log(CONST) self.def_write('mylib::print_stderr(') self.accept(args[0]) self.def_write(')') return quoted_fmt = PythonStringLiteral(args[0].value) self.def_write('mylib::print_stderr(StrFormat(%s, ' % quoted_fmt) for i, arg in enumerate(args[1:]): if i != 0: self.def_write(', ') self.accept(arg) self.def_write('))') def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> T: if o.callee.name == 'probe': self._ProbeExpr(o) return if o.callee.name == 'isinstance': self.report_error(o, 'isinstance() not allowed') return # return cast(ShArrayLiteral, tok) # -> return static_cast(tok) # TODO: Consolidate this with AssignmentExpr logic. if o.callee.name == 'cast': call = o type_expr = call.args[0] subtype_name = _GetCTypeForCast(type_expr) cast_kind = _GetCastKind(self.module_path, subtype_name) self.def_write('%s<%s>(', cast_kind, subtype_name) self.accept(call.args[1]) # variable being casted self.def_write(')') return # Translate printf-style varargs: # # log('foo %s', x) # => # log(StrFormat('foo %s', x)) if o.callee.name == 'log': self._LogExpr(o) return callee_name = o.callee.name if isinstance(o.callee, MemberExpr) and callee_name == 'next': self.accept(o.callee.expr) self.def_write('.iterNext') self._WriteArgList(o) return if self._IsInstantiation(o): self.def_write('Alloc<') self.accept(o.callee) self.def_write('>') self._WriteArgList(o) return # Namespace. if callee_name == 'int': # int('foo') in Python conflicts with keyword self.def_write('to_int') elif callee_name == 'float': self.def_write('to_float') elif callee_name == 'bool': self.def_write('to_bool') else: self.accept(o.callee) # could be f() or obj.method() self._WriteArgList(o) # TODO: look at keyword arguments! #self.log(' arg_kinds %s', o.arg_kinds) #self.log(' arg_names %s', o.arg_names) def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> T: # a + b when a and b are strings. (Can't use operator overloading # because they're pointers.) left_type = self.types[o.left] right_type = self.types[o.right] # NOTE: Need GetCType to handle Optional[BigStr*] in ASDL schemas. # Could tighten it up later. left_ctype = GetCType(left_type) right_ctype = GetCType(right_type) c_op = o.op if left_ctype == right_ctype == 'int' and c_op == '//': # integer division // -> / c_op = '/' # 'abc' + 'def' if left_ctype == right_ctype == 'BigStr*' and c_op == '+': self.def_write('str_concat(') self.accept(o.left) self.def_write(', ') self.accept(o.right) self.def_write(')') return # 'abc' * 3 if left_ctype == 'BigStr*' and right_ctype == 'int' and c_op == '*': self.def_write('str_repeat(') self.accept(o.left) self.def_write(', ') self.accept(o.right) self.def_write(')') return # [None] * 3 => list_repeat(None, 3) if left_ctype.startswith( 'List<') and right_ctype == 'int' and c_op == '*': self.def_write('list_repeat(') self.accept(o.left.items[0]) self.def_write(', ') self.accept(o.right) self.def_write(')') return # RHS can be primitive or tuple if left_ctype == 'BigStr*' and c_op == '%': self.def_write('StrFormat(') if isinstance(o.left, StrExpr): self.def_write(PythonStringLiteral(o.left.value)) else: self.accept(o.left) #log('right_type %s', right_type) if isinstance(right_type, Instance): fmt_types = [right_type] elif isinstance(right_type, TupleType): fmt_types = right_type.items # Handle Optional[str] elif (isinstance(right_type, UnionType) and len(right_type.items) == 2 and isinstance(right_type.items[1], NoneTyp)): fmt_types = [right_type.items[0]] else: raise AssertionError(right_type) # In the definition pass, write the call site. if isinstance(right_type, TupleType): for i, item in enumerate(o.right.items): self.def_write(', ') self.accept(item) else: # '[%s]' % x self.def_write(', ') self.accept(o.right) self.def_write(')') return # These parens are sometimes extra, but sometimes required. Example: # # if ((a and (false or true))) { # right # vs. # if (a and false or true)) { # wrong self.def_write('(') self.accept(o.left) self.def_write(' %s ', c_op) self.accept(o.right) self.def_write(')') def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> T: # Make sure it's binary assert len(o.operators) == 1, o.operators assert len(o.operands) == 2, o.operands operator = o.operators[0] left = o.operands[0] right = o.operands[1] # Assume is and is not are for None / nullptr comparison. if operator == 'is': # foo is None => foo == nullptr self.accept(o.operands[0]) self.def_write(' == ') self.accept(o.operands[1]) return if operator == 'is not': # foo is not None => foo != nullptr self.accept(o.operands[0]) self.def_write(' != ') self.accept(o.operands[1]) return # TODO: Change Optional[T] to T for our purposes? t0 = self.types[left] t1 = self.types[right] # 0: not a special case # 1: str # 2: Optional[str] which is Union[str, None] left_type = 0 # not a special case right_type = 0 # not a special case if IsStr(t0): left_type = 1 elif (isinstance(t0, UnionType) and len(t0.items) == 2 and IsStr(t0.items[0]) and isinstance(t0.items[1], NoneTyp)): left_type = 2 if IsStr(t1): right_type = 1 elif (isinstance(t1, UnionType) and len(t1.items) == 2 and IsStr(t1.items[0]) and isinstance(t1.items[1], NoneTyp)): right_type = 2 #self.log('left_type %s right_type %s', left_type, right_type) if left_type > 0 and right_type > 0 and operator in ('==', '!='): if operator == '!=': self.def_write('!(') # NOTE: This could also be str_equals(left, right)? Does it make a # difference? if left_type > 1 or right_type > 1: self.def_write('maybe_str_equals(') else: self.def_write('str_equals(') self.accept(left) self.def_write(', ') self.accept(right) self.def_write(')') if operator == '!=': self.def_write(')') return # Note: we could get rid of this altogether and rely on C++ function # overloading. But somehow I like it more explicit, closer to C (even # though we use templates). contains_func = _GetContainsFunc(t1) if operator == 'in': if isinstance(right, TupleExpr): left_type = self.types[left] equals_func = _EqualsFunc(left_type) # x in (1, 2, 3) => (x == 1 || x == 2 || x == 3) self.def_write('(') for i, item in enumerate(right.items): if i != 0: self.def_write(' || ') if equals_func: self.def_write('%s(' % equals_func) self.accept(left) self.def_write(', ') self.accept(item) self.def_write(')') else: self.accept(left) self.def_write(' == ') self.accept(item) self.def_write(')') return assert contains_func, "RHS of 'in' has type %r" % t1 # x in mylist => list_contains(mylist, x) self.def_write('%s(', contains_func) self.accept(right) self.def_write(', ') self.accept(left) self.def_write(')') return if operator == 'not in': if isinstance(right, TupleExpr): left_type = self.types[left] equals_func = _EqualsFunc(left_type) # x not in (1, 2, 3) => (x != 1 && x != 2 && x != 3) self.def_write('(') for i, item in enumerate(right.items): if i != 0: self.def_write(' && ') if equals_func: self.def_write('!%s(' % equals_func) self.accept(left) self.def_write(', ') self.accept(item) self.def_write(')') else: self.accept(left) self.def_write(' != ') self.accept(item) self.def_write(')') return assert contains_func, t1 # x not in mylist => !list_contains(mylist, x) self.def_write('!%s(', contains_func) self.accept(right) self.def_write(', ') self.accept(left) self.def_write(')') return # Default case self.accept(o.operands[0]) self.def_write(' %s ', o.operators[0]) self.accept(o.operands[1]) def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> T: pass def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> T: pass def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: self.not_translated(o, 'super expr') def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: self.not_translated(o, 'assign expr') def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: # e.g. a[-1] or 'not x' if o.op == 'not': op_str = '!' else: op_str = o.op self.def_write(op_str) self.accept(o.expr) def _WriteListElements(self, items, sep=', '): # sep may be 'COMMA' for a macro self.def_write('{') for i, item in enumerate(items): if i != 0: self.def_write(sep) self.accept(item) self.def_write('}') def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: list_type = self.types[o] #self.log('**** list_type = %s', list_type) c_type = GetCType(list_type) item_type = list_type.args[0] # int for List[int] item_c_type = GetCType(item_type) assert c_type.endswith('*'), c_type c_type = c_type[:-1] # HACK TO CLEAN UP if len(o.items) == 0: self.def_write('Alloc<%s>()' % c_type) else: self.def_write('NewList<%s>(std::initializer_list<%s>' % (item_c_type, item_c_type)) self._WriteListElements(o.items) self.def_write(')') def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> T: dict_type = self.types[o] c_type = GetCType(dict_type) assert c_type.endswith('*'), c_type c_type = c_type[:-1] # HACK TO CLEAN UP key_type, val_type = dict_type.args key_c_type = GetCType(key_type) val_c_type = GetCType(val_type) self.def_write('Alloc<%s>(' % c_type) #self.def_write('NewDict<%s, %s>(' % (key_c_type, val_c_type)) if o.items: keys = [k for k, _ in o.items] values = [v for _, v in o.items] self.def_write('std::initializer_list<%s>' % key_c_type) self._WriteListElements(keys) self.def_write(', ') self.def_write('std::initializer_list<%s>' % val_c_type) self._WriteListElements(values) self.def_write(')') def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> T: tuple_type = self.types[o] c_type = GetCType(tuple_type) assert c_type.endswith('*'), c_type c_type = c_type[:-1] # HACK TO CLEAN UP self.def_write('(Alloc<%s>(' % c_type) for i, item in enumerate(o.items): if i != 0: self.def_write(', ') self.accept(item) self.def_write('))') def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> T: self.not_translated(o, 'set expr') def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> T: self.accept(o.base) #base_type = self.types[o.base] #self.log('*** BASE TYPE %s', base_type) if isinstance(o.index, SliceExpr): self.accept(o.index) # method call else: # it's hard syntactically to do (*a)[0], so do it this way. if util.SMALL_STR: self.def_write('.at(') else: self.def_write('->at(') self.accept(o.index) self.def_write(')') def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> T: pass def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> T: pass def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: pass def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: pass def visit_dictionary_comprehension( self, o: 'mypy.nodes.DictionaryComprehension') -> T: pass def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: pass def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> T: self.def_write('->slice(') if o.begin_index: self.accept(o.begin_index) else: self.def_write('0') # implicit beginning if o.end_index: self.def_write(', ') self.accept(o.end_index) if o.stride: if not o.begin_index or not o.end_index: raise AssertionError( 'Stride only supported with beginning and ending index') self.def_write(', ') self.accept(o.stride) self.def_write(')') def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> T: if not _CheckCondition(o.cond, self.types): self.report_error( o, "Use explicit len(obj) or 'obj is not None' for mystr, mylist, mydict" ) return # 0 if b else 1 -> b ? 0 : 1 self.accept(o.cond) self.def_write(' ? ') self.accept(o.if_expr) self.def_write(' : ') self.accept(o.else_expr) def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: pass def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: pass def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> T: pass def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> T: pass def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> T: pass def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> T: pass def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> T: pass def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: pass def _write_tuple_unpacking(self, temp_name, lval_items, item_types, is_return=False): """Used by assignment and for loops.""" for i, (lval_item, item_type) in enumerate(zip(lval_items, item_types)): #self.log('*** %s :: %s', lval_item, item_type) if isinstance(lval_item, NameExpr): if _SkipAssignment(lval_item.name): continue # declare it at the top of the function if self.decl: self.local_var_list.append((lval_item.name, item_type)) self.def_write_ind('%s', lval_item.name) else: # Could be MemberExpr like self.foo, self.bar = baz self.def_write_ind('') self.accept(lval_item) # Tuples that are return values aren't pointers op = '.' if is_return else '->' self.def_write(' = %s%sat%d();\n', temp_name, op, i) # RHS def _ListComprehensionImpl(self, o, lval, c_type): """ Special case for list comprehensions. Note that the LHS MUST be on the LHS, so we can append to it. y = [i+1 for i in x[1:] if i] => y = [] for i in x[1:]: if i: y.append(i+1) (but in C++) """ gen = o.rvalue.generator # GeneratorExpr left_expr = gen.left_expr index_expr = gen.indices[0] seq = gen.sequences[0] cond = gen.condlists[0] # BUG: can't use this to filter # results = [x for x in results] if isinstance(seq, NameExpr) and seq.name == lval.name: raise AssertionError( "Can't use var %r in list comprehension because it would " "be overwritten" % lval.name) # Write empty container as initialization. assert c_type.endswith('*'), c_type # Hack self.def_write('Alloc<%s>();\n' % c_type[:-1]) over_type = self.types[seq] if over_type.type.fullname == 'builtins.list': c_type = GetCType(over_type) assert c_type.endswith('*'), c_type c_iter_type = c_type.replace('List', 'ListIter', 1)[:-1] # remove * else: # Example: assoc == Optional[Dict[str, str]] c_iter_type = 'TODO_ASSOC' self.def_write_ind('for (%s it(', c_iter_type) self.accept(seq) self.def_write('); !it.Done(); it.Next()) {\n') item_type = over_type.args[0] # get 'int' from 'List' if isinstance(item_type, Instance): self.def_write_ind(' %s ', GetCType(item_type)) # TODO(StackRoots): for ch in 'abc' self.accept(index_expr) self.def_write(' = it.Value();\n') elif isinstance(item_type, TupleType): # for x, y in pairs c_item_type = GetCType(item_type) if isinstance(index_expr, TupleExpr): temp_name = 'tup%d' % self.unique_id self.unique_id += 1 self.def_write_ind(' %s %s = it.Value();\n', c_item_type, temp_name) self.indent += 1 self._write_tuple_unpacking(temp_name, index_expr.items, item_type.items) self.indent -= 1 else: raise AssertionError() else: raise AssertionError('Unexpected type %s' % item_type) if cond: self.indent += 1 self.def_write_ind('if (') self.accept(cond[0]) # Just the first one self.def_write(') {\n') self.def_write_ind(' %s->append(', lval.name) self.accept(left_expr) self.def_write(');\n') if cond: self.def_write_ind('}\n') self.indent -= 1 self.def_write_ind('}\n') def _AssignNewDictImpl(self, lval): """ d = NewDict() # type: Dict[int, int] -> auto* d = NewDict(); - NewDict exists in Python, it makes ordered dictionaries - We translate it here because we need type inference I think we could get rid of NewDict in C++, and have it only in Python. We used to have the "allocating in a constructor" rooting problem, but I believe that's gone now. """ lval_type = self.types[lval] # Fix for Dict[str, value]? in ASDL #self.log('lval type %s', lval_type) if (isinstance(lval_type, UnionType) and len(lval_type.items) == 2 and isinstance(lval_type.items[1], NoneTyp)): lval_type = lval_type.items[0] c_type = GetCType(lval_type) if self.decl: self.local_var_list.append((lval.name, lval_type)) assert c_type.endswith('*') # Hack for declaration vs. definition. TODO: clean this up prefix = '' if self.current_func_node else 'auto* ' self.def_write_ind('%s%s = Alloc<%s>();\n', prefix, lval.name, c_type[:-1]) def _AssignCastImpl(self, o, lval): """ is_downcast_and_shadow idiom: src = cast(source__SourcedFile, UP_src) -> source__SourcedFile* src = static_cast(UP_src) """ assert isinstance(lval, NameExpr) call = o.rvalue type_expr = call.args[0] subtype_name = _GetCTypeForCast(type_expr) cast_kind = _GetCastKind(self.module_path, subtype_name) is_downcast_and_shadow = False to_cast = call.args[1] if isinstance(to_cast, NameExpr): if to_cast.name.startswith('UP_'): is_downcast_and_shadow = True if is_downcast_and_shadow: # Declare NEW local variable inside case, which shadows it self.def_write_ind('%s %s = %s<%s>(', subtype_name, lval.name, cast_kind, subtype_name) else: # Normal variable if self.decl: self.local_var_list.append((lval.name, subtype_name)) self.def_write_ind('%s = %s<%s>(', lval.name, cast_kind, subtype_name) self.accept(call.args[1]) # variable being casted self.def_write(');\n') def _IteratorImpl(self, o, lval, rval_type): # We're calling a generator. Create a temporary List on the stack # to accumulate the results in one big batch, then wrap it in # ListIter. assert len(rval_type.args) == 1, rval_type.args c_type = GetCType(rval_type) type_param = rval_type.args[0] inner_c_type = GetCType(type_param) iter_buf = ('_iter_buf_%s' % lval.name, 'List<%s>*' % inner_c_type) self.def_write_ind('List<%s> %s;\n', inner_c_type, iter_buf[0]) self.current_stmt_node = o self.yield_accumulators[o] = iter_buf self.def_write_ind('') self.accept(o.rvalue) self.current_stmt_node = None self.def_write(';\n') self.def_write_ind('%s %s(&%s);\n', c_type, lval.name, iter_buf[0]) def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: # Declare constant strings. They have to be at the top level. if self.decl and self.indent == 0 and len(o.lvalues) == 1: lval = o.lvalues[0] c_type = GetCType(self.types[lval]) if not _SkipAssignment(lval.name): self.always_write('extern %s %s;\n', c_type, lval.name) # I think there are more than one when you do a = b = 1, which I never # use. assert len(o.lvalues) == 1, o.lvalues lval = o.lvalues[0] # GLOBAL CONSTANTS - Avoid Alloc, since that can't be done until main(). if self.indent == 0: assert isinstance(lval, NameExpr), lval if _SkipAssignment(lval.name): return #self.log(' GLOBAL: %s', lval.name) lval_type = self.types[lval] # Global # L = [1, 2] # type: List[int] if isinstance(o.rvalue, ListExpr): item_type = lval_type.args[0] item_c_type = GetCType(item_type) # Any constant strings will have already been written # TODO: Assert that every item is a constant? self.def_write('GLOBAL_LIST(%s, %s, %d, ', lval.name, item_c_type, len(o.rvalue.items)) self._WriteListElements(o.rvalue.items, sep=' COMMA ') self.def_write(');\n') return # Global # D = {"foo": "bar"} # type: Dict[str, str] if isinstance(o.rvalue, DictExpr): key_type, val_type = lval_type.args key_c_type = GetCType(key_type) val_c_type = GetCType(val_type) dict_expr = o.rvalue self.def_write('GLOBAL_DICT(%s, %s, %s, %d, ', lval.name, key_c_type, val_c_type, len(dict_expr.items)) keys = [k for k, _ in dict_expr.items] values = [v for _, v in dict_expr.items] self._WriteListElements(keys, sep=' COMMA ') self.def_write(', ') self._WriteListElements(values, sep=' COMMA ') self.def_write(');\n') return # We could do GcGlobal<> for ASDL classes, but Oils doesn't use them if isinstance(o.rvalue, CallExpr): self.report_error( o, "Can't initialize objects at the top level, only BigStr List Dict" ) return # myconst = 1 << 3 => myconst = 1 << 3 is currently allowed # # Non-top-level # if isinstance(o.rvalue, CallExpr): callee = o.rvalue.callee if callee.name == 'NewDict': self._AssignNewDictImpl(lval) return if callee.name == 'cast': self._AssignCastImpl(o, lval) return rval_type = self.types[o.rvalue] if (isinstance(rval_type, Instance) and rval_type.type.fullname == 'typing.Iterator'): self._IteratorImpl(o, lval, rval_type) return if isinstance(lval, NameExpr): lval_type = self.types[lval] #c_type = GetCType(lval_type, local=self.indent != 0) c_type = GetCType(lval_type) # for "hoisting" to the top of the function if self.current_func_node: self.def_write_ind('%s = ', lval.name) if self.decl: self.local_var_list.append((lval.name, lval_type)) else: # globals always get a type -- they're not mutated self.def_write_ind('%s %s = ', c_type, lval.name) if isinstance(o.rvalue, ListComprehension): self._ListComprehensionImpl(o, lval, c_type) return self.accept(o.rvalue) self.def_write(';\n') return if isinstance(lval, MemberExpr): # self.x = foo self.def_write_ind('') self.accept(lval) self.def_write(' = ') self.accept(o.rvalue) self.def_write(';\n') if self.current_method_name in ('__init__', 'Reset'): # Collect statements that look like self.foo = 1 # Only do this in __init__ so that a derived class mutating a field # from the base class doesn't cause duplicate C++ fields. (C++ # allows two fields of the same name!) # # HACK for WordParser: also include Reset(). We could change them # all up front but I kinda like this. if (isinstance(lval.expr, NameExpr) and lval.expr.name == 'self'): #log(' lval.name %s', lval.name) lval_type = self.types[lval] c_type = GetCType(lval_type) is_managed = CTypeIsManaged(c_type) self.current_member_vars[lval.name] = (lval_type, c_type, is_managed) return if isinstance(lval, IndexExpr): # a[x] = 1 # d->set(x, 1) for both List and Dict self.def_write_ind('') self.accept(lval.base) self.def_write('->set(') self.accept(lval.index) self.def_write(', ') self.accept(o.rvalue) self.def_write(');\n') return if isinstance(lval, TupleExpr): # An assignment to an n-tuple turns into n+1 statements. Example: # # x, y = mytuple # # Tuple2 tup1 = mytuple # int x = tup1->at0() # BigStr* y = tup1->at1() rvalue_type = self.types[o.rvalue] # type alias upgrade for MyPy 0.780 if isinstance(rvalue_type, TypeAliasType): rvalue_type = rvalue_type.alias.target c_type = GetCType(rvalue_type) is_return = (isinstance(o.rvalue, CallExpr) and o.rvalue.callee.name != "next") if is_return: assert c_type.endswith('*') c_type = c_type[:-1] temp_name = 'tup%d' % self.unique_id self.unique_id += 1 self.def_write_ind('%s %s = ', c_type, temp_name) self.accept(o.rvalue) self.def_write(';\n') self._write_tuple_unpacking(temp_name, lval.items, rvalue_type.items, is_return=is_return) return raise AssertionError(lval) def _write_body(self, body): """Write a block without the { }.""" for stmt in body: # Ignore things that look like docstrings if (isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr)): continue #log('-- %d', self.indent) self.accept(stmt) def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> T: if 0: self.log('ForStmt') self.log(' index_type %s', o.index_type) self.log(' inferred_item_type %s', o.inferred_item_type) self.log(' inferred_iterator_type %s', o.inferred_iterator_type) func_name = None # does the loop look like 'for x in func():' ? if (isinstance(o.expr, CallExpr) and isinstance(o.expr.callee, NameExpr)): func_name = o.expr.callee.name # special case: 'for i in xrange(3)' if func_name == 'xrange': index_name = o.index.name args = o.expr.args num_args = len(args) if num_args == 1: # xrange(end) self.def_write_ind('for (int %s = 0; %s < ', index_name, index_name) self.accept(args[0]) self.def_write('; ++%s) ', index_name) elif num_args == 2: # xrange(being, end) self.def_write_ind('for (int %s = ', index_name) self.accept(args[0]) self.def_write('; %s < ', index_name) self.accept(args[1]) self.def_write('; ++%s) ', index_name) elif num_args == 3: # xrange(being, end, step) # Special case to detect a constant -1. This is a static # heuristic, because it could be negative dynamically. TODO: # mylib.reverse_xrange() or something? step = args[2] if isinstance(step, UnaryExpr) and step.op == '-': comparison_op = '>' else: comparison_op = '<' self.def_write_ind('for (int %s = ', index_name) self.accept(args[0]) self.def_write('; %s %s ', index_name, comparison_op) self.accept(args[1]) self.def_write('; %s += ', index_name) self.accept(step) self.def_write(') ') else: raise AssertionError() self.accept(o.body) return reverse = False # for i, x in enumerate(...): index0_name = None if func_name == 'enumerate': assert isinstance(o.index, TupleExpr), o.index index0 = o.index.items[0] assert isinstance(index0, NameExpr), index0 index0_name = index0.name # generate int i = 0; ; ++i # type of 'x' in 'for i, x in enumerate(...)' item_type = o.inferred_item_type.items[1] index_expr = o.index.items[1] # enumerate(mylist) turns into iteration over mylist with variable i assert len(o.expr.args) == 1, o.expr.args iterated_over = o.expr.args[0] elif func_name == 'reversed': # NOTE: enumerate() and reversed() can't be mixed yet. But you CAN # reverse iter over tuples. item_type = o.inferred_item_type index_expr = o.index args = o.expr.args assert len(args) == 1, args iterated_over = args[0] reverse = True # use different iterate elif func_name == 'iteritems': item_type = o.inferred_item_type index_expr = o.index args = o.expr.args assert len(args) == 1, args # This should be a dict iterated_over = args[0] #log('------------ ITERITEMS OVER %s', iterated_over) else: item_type = o.inferred_item_type index_expr = o.index iterated_over = o.expr over_type = self.types[iterated_over] if isinstance(over_type, TypeAliasType): over_type = over_type.alias.target #self.log(' iterating over type %s', over_type) #self.log(' iterating over type %s', over_type.type.fullname) over_dict = False yield_acc = None if over_type.type.fullname == 'builtins.list': c_type = GetCType(over_type) assert c_type.endswith('*'), c_type c_iter_type = c_type.replace('List', 'ListIter', 1)[:-1] # remove * # ReverseListIter! if reverse: c_iter_type = 'Reverse' + c_iter_type elif over_type.type.fullname == 'builtins.dict': # Iterator c_type = GetCType(over_type) assert c_type.endswith('*'), c_type c_iter_type = c_type.replace('Dict', 'DictIter', 1)[:-1] # remove * over_dict = True assert not reverse elif over_type.type.fullname == 'builtins.str': c_iter_type = 'StrIter' assert not reverse # can't reverse iterate over string yet elif over_type.type.fullname == 'typing.Iterator': # We're iterating over a generator. Create a temporary List on the stack # to accumulate the results in one big batch. c_iter_type = GetCType(over_type) assert len(over_type.args) == 1, over_type.args inner_c_type = GetCType(over_type.args[0]) yield_acc = ('_for_yield_acc%d' % self.unique_id, 'List<%s>*' % inner_c_type) self.unique_id += 1 self.def_write_ind('List<%s> %s;\n', inner_c_type, yield_acc[0]) self.def_write_ind('') self.yield_accumulators[o] = yield_acc self.current_stmt_node = o self.accept(iterated_over) self.current_stmt_node = None self.def_write(';\n') else: # assume it's like d.iteritems()? Iterator type assert False, over_type if index0_name: # can't initialize two things in a for loop, so do it on a separate line if self.decl: self.local_var_list.append((index0_name, 'int')) self.def_write_ind('%s = 0;\n', index0_name) index_update = ', ++%s' % index0_name else: index_update = '' self.def_write_ind('for (%s it(', c_iter_type) if yield_acc: self.def_write('&%s', yield_acc[0]) else: self.accept(iterated_over) # the thing being iterated over self.def_write('); !it.Done(); it.Next()%s) {\n', index_update) # for x in it: ... # for i, x in enumerate(pairs): ... if isinstance(item_type, Instance) or index0_name: c_item_type = GetCType(item_type) self.def_write_ind(' %s ', c_item_type) self.accept(index_expr) if over_dict: self.def_write(' = it.Key();\n') else: self.def_write(' = it.Value();\n') # Register loop variable as a stack root. # Note we have mylib.Collect() in CommandEvaluator::_Execute(), and # it's called in a loop by _ExecuteList(). Although the 'child' # variable is already live by other means. # TODO: Test how much this affects performance. if CTypeIsManaged(c_item_type): self.def_write_ind(' StackRoot _for(&') self.accept(index_expr) self.def_write_ind(');\n') elif isinstance(item_type, TupleType): # for x, y in pairs if over_dict: assert isinstance(o.index, TupleExpr), o.index index_items = o.index.items assert len(index_items) == 2, index_items assert len(item_type.items) == 2, item_type.items key_type = GetCType(item_type.items[0]) val_type = GetCType(item_type.items[1]) #log('** %s key_type %s', item_type.items[0], key_type) #log('** %s val_type %s', item_type.items[1], val_type) # TODO(StackRoots): k, v self.def_write_ind(' %s %s = it.Key();\n', key_type, index_items[0].name) self.def_write_ind(' %s %s = it.Value();\n', val_type, index_items[1].name) else: # Example: # for (ListIter it(mylist); !it.Done(); it.Next()) { # Tuple2 tup1 = it.Value(); # int i = tup1->at0(); # BigStr* s = tup1->at1(); # log("%d %s", i, s); # } c_item_type = GetCType(item_type) if isinstance(o.index, TupleExpr): # TODO(StackRoots) temp_name = 'tup%d' % self.unique_id self.unique_id += 1 self.def_write_ind(' %s %s = it.Value();\n', c_item_type, temp_name) self.indent += 1 self._write_tuple_unpacking(temp_name, o.index.items, item_type.items) self.indent -= 1 else: self.def_write_ind(' %s %s = it.Value();\n', c_item_type, o.index.name) #self.def_write_ind(' StackRoots _for(&%s)\n;', o.index.name) else: raise AssertionError('Unexpected type %s' % item_type) # Copy of visit_block, without opening { self.indent += 1 block = o.body self._write_body(block.body) self.indent -= 1 self.def_write_ind('}\n') if o.else_body: raise AssertionError("can't translate for-else") def _write_cases(self, switch_expr, cases, default_block): """ Write a list of (expr, block) pairs """ for expr, body in cases: assert expr is not None, expr if not isinstance(expr, CallExpr): self.report_error(expr, 'Expected call like case(x), got %s' % expr) return for i, arg in enumerate(expr.args): if i != 0: self.def_write('\n') self.def_write_ind('case ') self.accept(arg) self.def_write(': ') self.accept(body) self.def_write_ind(' break;\n') if default_block is None: # an error occurred return if default_block is False: # This is too restrictive #self.report_error(switch_expr, # 'switch got no else: for default block') return self.def_write_ind('default: ') self.accept(default_block) # don't write 'break' def _write_switch(self, expr, o): """Write a switch statement over integers.""" assert len(expr.args) == 1, expr.args self.def_write_ind('switch (') self.accept(expr.args[0]) self.def_write(') {\n') assert len(o.body.body) == 1, o.body.body if_node = o.body.body[0] assert isinstance(if_node, IfStmt), if_node self.indent += 1 cases = [] default_block = util._collect_cases(self.module_path, if_node, cases, errors=self.errors_keep_going) self._write_cases(expr, cases, default_block) self.indent -= 1 self.def_write_ind('}\n') def _write_tag_switch(self, expr, o): """Write a switch statement over ASDL types.""" assert len(expr.args) == 1, expr.args self.def_write_ind('switch (') self.accept(expr.args[0]) self.def_write('->tag()) {\n') assert len(o.body.body) == 1, o.body.body if_node = o.body.body[0] assert isinstance(if_node, IfStmt), if_node self.indent += 1 cases = [] default_block = util._collect_cases(self.module_path, if_node, cases, errors=self.errors_keep_going) self._write_cases(expr, cases, default_block) self.indent -= 1 self.def_write_ind('}\n') def _str_switch_cases(self, cases): cases2 = [] for expr, body in cases: if not isinstance(expr, CallExpr): # non-fatal check from _collect_cases break args = expr.args if len(args) != 1: self.report_error( expr, 'str_switch can only have case("x"), not case("x", "y")' % args) break if not isinstance(args[0], StrExpr): self.report_error( expr, 'str_switch can only be used with constant strings, got %s' % args[0]) break s = args[0].value cases2.append((len(s), s, body)) # Sort by string length cases2.sort(key=lambda pair: pair[0]) grouped = itertools.groupby(cases2, key=lambda pair: pair[0]) return grouped def _write_str_switch(self, expr, o): """Write a switch statement over strings.""" assert len(expr.args) == 1, expr.args switch_expr = expr # for later error switch_var = expr.args[0] if not isinstance(switch_var, NameExpr): self.report_error( expr.args[0], 'str_switch(x) accepts only a variable name, got %s' % switch_var) return self.def_write_ind('switch (len(%s)) {\n' % switch_var.name) # There can only be one thing under 'with str_switch' assert len(o.body.body) == 1, o.body.body if_node = o.body.body[0] assert isinstance(if_node, IfStmt), if_node self.indent += 1 cases = [] default_block = util._collect_cases(self.module_path, if_node, cases, errors=self.errors_keep_going) grouped_cases = self._str_switch_cases(cases) # Warning: this consumes internal iterator #self.log('grouped %s', list(grouped_cases)) for str_len, group in grouped_cases: self.def_write_ind('case %s: {\n' % str_len) if_num = 0 for _, case_str, block in group: self.indent += 1 else_str = '' if if_num == 0 else 'else ' self.def_write_ind('%sif (str_equals_c(%s, %s, %d)) ' % (else_str, switch_var.name, PythonStringLiteral(case_str), str_len)) self.accept(block) self.indent -= 1 if_num += 1 self.indent += 1 self.def_write_ind('else {\n') self.def_write_ind(' goto str_switch_default;\n') self.def_write_ind('}\n') self.indent -= 1 self.def_write_ind('}\n') self.def_write_ind(' break;\n') if default_block is None: # an error occurred return if default_block is False: self.report_error(switch_expr, 'str_switch got no else: for default block') return self.def_write('\n') self.def_write_ind('str_switch_default:\n') self.def_write_ind('default: ') self.accept(default_block) self.indent -= 1 self.def_write_ind('}\n') def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> T: """ Translate only blocks of this form: with switch(x) as case: if case(0): print('zero') elif case(1, 2, 3): print('low') else: print('other') switch(x) { case 0: # TODO: need casting here print('zero') break; case 1: case 2: case 3: print('low') break; default: print('other') break; } Or: with ctx_Bar(bar, x, y): x() { ctx_Bar(bar, x, y) x(); } """ #log('WITH') #log('expr %s', o.expr) #log('target %s', o.target) assert len(o.expr) == 1, o.expr expr = o.expr[0] assert isinstance(expr, CallExpr), expr callee_name = expr.callee.name if callee_name == 'switch': self._write_switch(expr, o) elif callee_name == 'str_switch': self._write_str_switch(expr, o) elif callee_name == 'tagswitch': self._write_tag_switch(expr, o) else: assert isinstance(expr, CallExpr), expr self.def_write_ind('{ // with\n') self.indent += 1 self.def_write_ind('') self.accept(expr.callee) # FIX: Use braced initialization to avoid most-vexing parse when # there are 0 args! self.def_write(' ctx{') for i, arg in enumerate(expr.args): if i != 0: self.def_write(', ') self.accept(arg) self.def_write('};\n\n') #self.def_write_ind('') self._write_body(o.body.body) self.indent -= 1 self.def_write_ind('}\n') def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> T: d = o.expr if isinstance(d, IndexExpr): self.def_write_ind('') self.accept(d.base) if isinstance(d.index, SliceExpr): # del mylist[:] -> mylist->clear() sl = d.index assert sl.begin_index is None, sl assert sl.end_index is None, sl self.def_write('->clear()') else: # del mydict[mykey] raises KeyError, which we don't want raise AssertionError( 'Use mylib.maybe_remove(d, key) instead of del d[key]') self.def_write(';\n') def _WriteFuncParams(self, arg_types, arguments, update_locals=False, write_defaults=False): """Write params for function/method signatures. Optionally mutate self.local_vars, and optionally write default arguments. """ if write_defaults: # Check if default args are valid first num_defaults = 0 for arg in arguments: if arg.initializer: t = self.types[arg.initializer] valid = False if isinstance(t, NoneType): valid = True if isinstance(t, Instance): # Allowing strings since they're immutable, e.g. # prefix='' seems OK if t.type.fullname in ('builtins.bool', 'builtins.int', 'builtins.float', 'builtins.str'): valid = True # ASDL enums lex_mode_t, scope_t, ... if t.type.fullname.endswith('_t'): valid = True # Hack for loc__Missing. Should detect the general case. if t.type.fullname.endswith('loc__Missing'): valid = True if not valid: self.report_error( arg, 'Invalid default arg %r of type %s (not None, bool, int, float, ASDL enum)' % (arg.initializer, t)) return num_defaults += 1 if num_defaults > 1: name = '[TODO]' #if class_name: # name = '%s::%s' % (class_name, func_name) #else: # name = func_name # Report on first arg self.report_error( arg, '%s has %d default arguments. Only 1 is allowed' % (name, num_defaults)) return first = True # first NOT including self for arg_type, arg in zip(arg_types, arguments): if not first: self.always_write(', ') # TODO: Turn this on. Having stdlib problems, e.g. # examples/cartesian. c_type = GetCType(arg_type, param=False) arg_name = arg.variable.name # C++ has implicit 'this' if arg_name == 'self': continue self.always_write('%s %s', c_type, arg_name) if write_defaults and arg.initializer: self.always_write(' = ') # Silly mechanism to activate self.def_write() self.writing_default_arg = True self.accept(arg.initializer) self.writing_default_arg = False first = False # Params are locals. There are 4 callers to _WriteFuncParams and we # only do it in one place. TODO: Check if locals are used in # __init__ after allocation. if update_locals: self.local_var_list.append((arg_name, arg_type)) # We can't use __str__ on these Argument objects? That seems like an # oversight #self.log('%r', arg) if 0: self.log('Argument %s', arg.variable) self.log(' type_annotation %s', arg.type_annotation) # I think these are for default values self.log(' initializer %s', arg.initializer) self.log(' kind %s', arg.kind) # Will be set if we're declaring or defining a function that returns # Iterator[T]. if self.current_func_node in self.yield_accumulators: if not first: self.always_write(', ') arg_name, c_type = self.yield_accumulators[self.current_func_node] self.always_write('%s %s', c_type, arg_name) def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> T: if o.name == '__repr__': # Don't translate return # No function prototypes when forward declaring. if self.forward_decl: self.virtual.OnMethod(self.current_class_name, o.name) return func_name = o.name virtual = '' if self.decl: self.local_var_list = [] # Make a new instance to collect from self.local_vars[o] = self.local_var_list if self.virtual.IsVirtual(self.current_class_name, o.name): virtual = 'virtual ' if not self.decl and self.current_class_name: # definition looks like # void Class::method(...); func_name = join_name((self.current_class_name[-1], o.name)) else: # declaration inside class { } func_name = o.name self.def_write('\n') c_ret_type, _, c_iter_list_type = GetCReturnType(o.type.ret_type) if c_iter_list_type is not None: # The function is a generator. Add an output param that references an # accumulator for the results. self.yield_accumulators[o] = ('_out_yield_acc', c_iter_list_type) # Avoid C++ warnings by prepending [[noreturn]] noreturn = '' if func_name in ('e_die', 'e_die_status', 'e_strict', 'e_usage', 'p_die'): noreturn = '[[noreturn]] ' self.always_write_ind('%s%s%s %s(', noreturn, virtual, c_ret_type, func_name) self.current_func_node = o self._WriteFuncParams( o.type.arg_types, o.arguments, update_locals=True, # write default values in the declaration only write_defaults=self.decl) if self.decl: self.always_write(');\n') else: self.def_write(') ') if not self.forward_decl: arg_names = [arg.variable.name for arg in o.arguments] #log('arg_names %s', arg_names) #log('local_vars %s', self.local_vars[o]) self.prepend_to_block = [] for (lval_name, lval_type) in self.local_vars[o]: c_type = lval_type if not isinstance(lval_type, str): c_type = GetCType(lval_type) self.prepend_to_block.append((lval_name, c_type, lval_name in arg_names)) self.accept(o.body) self.current_func_node = None def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> T: pass def _TracingMetadataDecl(self, o, field_gc, mask_bits): if mask_bits: self.always_write_ind('\n') self.always_write_ind('static constexpr uint32_t field_mask() {\n') self.always_write_ind(' return ') for i, b in enumerate(mask_bits): if i != 0: self.always_write('\n') self.always_write_ind(' | ') self.always_write(b) self.always_write(';\n') self.always_write_ind('}\n') obj_tag, obj_arg = field_gc if obj_tag == 'HeapTag::FixedSize': obj_mask = obj_arg obj_header = 'ObjHeader::ClassFixed(%s, sizeof(%s))' % (obj_mask, o.name) elif obj_tag == 'HeapTag::Scanned': num_pointers = obj_arg obj_header = 'ObjHeader::ClassScanned(%s, sizeof(%s))' % ( num_pointers, o.name) else: raise AssertionError(o.name) self.always_write('\n') self.always_write_ind('static constexpr ObjHeader obj_header() {\n') self.always_write_ind(' return %s;\n' % obj_header) self.always_write_ind('}\n') def _MemberDecl(self, o, base_class_name): # List of field mask expressions mask_bits = [] if self.virtual.CanReorderFields(split_py_name(o.fullname)): # No inheritance, so we are free to REORDER member vars, putting # pointers at the front. pointer_members = [] non_pointer_members = [] for name in self.current_member_vars: _, c_type, is_managed = self.current_member_vars[name] if is_managed: pointer_members.append(name) else: non_pointer_members.append(name) # So we declare them in the right order sorted_member_names = pointer_members + non_pointer_members field_gc = ('HeapTag::Scanned', len(pointer_members)) else: # Has inheritance # The field mask of a derived class is unioned with its base's # field mask. if base_class_name: mask_bits.append( '%s::field_mask()' % join_name(base_class_name, strip_package=True)) for name in sorted(self.current_member_vars): _, c_type, is_managed = self.current_member_vars[name] if is_managed: mask_bits.append('maskbit(offsetof(%s, %s))' % (o.name, name)) # A base class with no fields has kZeroMask. if not base_class_name and not mask_bits: mask_bits.append('kZeroMask') sorted_member_names = sorted(self.current_member_vars) field_gc = ('HeapTag::FixedSize', 'field_mask()') # Write member variables #log('MEMBERS for %s: %s', o.name, list(self.current_member_vars.keys())) if len(self.current_member_vars): if base_class_name: self.always_write('\n') # separate from functions for name in sorted_member_names: _, c_type, _ = self.current_member_vars[name] self.always_write_ind('%s %s;\n', c_type, name) if _IsContextManager(self.current_class_name): # Copy ctx member vars out of this class assert self.ctx_member_vars is not None self.ctx_member_vars[o] = dict(self.current_member_vars) else: self._TracingMetadataDecl(o, field_gc, mask_bits) self.always_write('\n') self.always_write_ind('DISALLOW_COPY_AND_ASSIGN(%s)\n', o.name) self.indent -= 1 self.always_write_ind('};\n') self.always_write('\n') def _ClassDefDecl(self, o, base_class_name): self.current_member_vars.clear() # make a new list self.always_write_ind('class %s', o.name) # block after this # e.g. class TextOutput : public ColorOutput if base_class_name: self.always_write(' : public %s', join_name(base_class_name, strip_package=True)) self.always_write(' {\n') self.always_write_ind(' public:\n') block = o.defs self.indent += 1 for stmt in block.body: # Ignore things that look like docstrings if (isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr)): continue # Constructor is named after class if isinstance(stmt, FuncDef): method_name = stmt.name if method_name == '__init__': self.always_write_ind('%s(', o.name) self._WriteFuncParams(stmt.type.arg_types, stmt.arguments, write_defaults=True) self.always_write(');\n') # Visit for member vars self.current_method_name = method_name self.accept(stmt.body) self.current_method_name = None continue if method_name == '__enter__': continue if method_name == '__exit__': # Turn it into a destructor with NO ARGS self.always_write_ind('~%s();\n', o.name) continue if method_name == '__repr__': # skip during declaration, just like visit_func_def does during definition continue # Any other function: Visit for member vars self.current_method_name = method_name self.accept(stmt) self.current_method_name = None continue # TODO: Remove this? Everything under a class is a method? self.accept(stmt) self._MemberDecl(o, base_class_name) def _ConstructorImpl(self, o, stmt, base_class_name): self.def_write('\n') self.def_write('%s::%s(', o.name, o.name) self._WriteFuncParams(stmt.type.arg_types, stmt.arguments) self.def_write(')') first_index = 0 # Skip docstring maybe_skip_stmt = stmt.body.body[0] if (isinstance(maybe_skip_stmt, ExpressionStmt) and isinstance(maybe_skip_stmt.expr, StrExpr)): first_index += 1 # Check for Base.__init__(self, ...) and move that to the initializer list. first_stmt = stmt.body.body[first_index] if (isinstance(first_stmt, ExpressionStmt) and isinstance(first_stmt.expr, CallExpr)): expr = first_stmt.expr #log('expr %s', expr) callee = first_stmt.expr.callee # TextOutput() : ColorOutput(f), ... { if (isinstance(callee, MemberExpr) and callee.name == '__init__'): base_constructor_args = expr.args #log('ARGS %s', base_constructor_args) self.def_write(' : %s(', join_name(base_class_name, strip_package=True)) for i, arg in enumerate(base_constructor_args): if i == 0: continue # Skip 'this' if i != 1: self.def_write(', ') self.accept(arg) self.def_write(')') first_index += 1 self.def_write(' {\n') # Now visit the rest of the statements self.indent += 1 if _IsContextManager(self.current_class_name): # For ctx_* classes only, do gHeap.PushRoot() for all the pointer # members member_vars = self.ctx_member_vars[o] for name in sorted(member_vars): _, c_type, is_managed = member_vars[name] if is_managed: # VALIDATE_ROOTS doesn't complain even if it's not # initialized? Should be initialized after PushRoot(). #self.def_write_ind('this->%s = nullptr;\n' % name) self.def_write_ind( 'gHeap.PushRoot(reinterpret_cast(&(this->%s)));\n' % name) for node in stmt.body.body[first_index:]: self.accept(node) self.indent -= 1 self.def_write('}\n') def _DestructorImpl(self, o, stmt, base_class_name): self.always_write('\n') self.always_write_ind('%s::~%s()', o.name, o.name) self.def_write(' {\n') self.indent += 1 # TODO: # - Can't throw exception in destructor. # - Check that you don't return early from destructor. If so, we skip # PopRoot(), which messes up the invariant! for node in stmt.body.body: self.accept(node) # For ctx_* classes only , gHeap.PopRoot() for all the pointer members if _IsContextManager(self.current_class_name): member_vars = self.ctx_member_vars[o] for name in sorted(member_vars): _, c_type, is_managed = member_vars[name] if is_managed: self.def_write_ind('gHeap.PopRoot();\n') else: self.report_error( o, 'Any class with __exit__ should be named ctx_Foo (%s)' % (self.current_class_name, )) return self.indent -= 1 self.def_write('}\n') def _ClassDefImpl(self, o, base_class_name): block = o.defs for stmt in block.body: if isinstance(stmt, FuncDef): if stmt.name == '__init__': self._ConstructorImpl(o, stmt, base_class_name) continue if stmt.name == '__enter__': # We never use these continue if stmt.name == '__exit__': self._DestructorImpl(o, stmt, base_class_name) continue self.accept(stmt) def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: #log(' CLASS %s', o.name) base_class_name = None # single inheritance only for b in o.base_type_exprs: if isinstance(b, NameExpr): # TODO: inherit from std::exception? if b.name != 'object' and b.name != 'Exception': base_class_name = split_py_name(b.fullname) elif isinstance(b, MemberExpr): # vm._Executor -> vm::_Executor assert isinstance(b.expr, NameExpr), b base_class_name = split_py_name(b.expr.fullname) + (b.name, ) # Forward declare types because they may be used in prototypes if self.forward_decl: self.always_write_ind('class %s;\n', o.name) if base_class_name: self.virtual.OnSubclass(base_class_name, split_py_name(o.fullname)) # Visit class body so we get method declarations self.current_class_name = split_py_name(o.fullname) self._write_body(o.defs.body) self.current_class_name = None return if self.decl: self.current_class_name = split_py_name(o.fullname) self._ClassDefDecl(o, base_class_name) self.current_class_name = None return self.current_class_name = split_py_name(o.fullname) self._ClassDefImpl(o, base_class_name) self.current_class_name = None # Stop prefixing functions with class def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> T: self.report_error(o, 'global not allowed') def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> T: pass def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: pass def visit_var(self, o: 'mypy.nodes.Var') -> T: pass # Module structure def visit_import(self, o: 'mypy.nodes.Import') -> T: pass def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> T: """ Write C++ namespace aliases and 'using' for imports. We need them in the 'decl' phase for default arguments like runtime_asdl::scope_e -> scope_e """ if o.id in ('__future__', 'typing'): return # do nothing #self.log(' %s ImportFrom id: %s', self.decl, o.id) for name, alias in o.names: #self.log('ImportFrom id: %s name: %s alias: %s', o.id, name, alias) if name == 'log': # varargs translation continue if name == 'stderr_line': # TODO: remove this continue if o.id == 'mycpp.mylib': # These mylib functions are translated in a special way if name in ('switch', 'tagswitch', 'str_switch', 'iteritems', 'NewDict', 'probe'): continue # STDIN_FILENO is #included if name == 'STDIN_FILENO': continue # A heuristic that works for the Oil import style. if '.' in o.id: # from mycpp.mylib import log => using mylib::log translate_import = True else: # from core import util => NOT translated # We just rely on 'util' being defined. translate_import = False if translate_import: dotted_parts = o.id.split('.') last_dotted = dotted_parts[-1] # Omit these: # from _gen.ysh import grammar_nt if last_dotted == 'ysh': return # from _devbuild.gen import syntax_asdl if last_dotted == 'gen': return # Problem: # - The decl stage has to return yaks_asdl::mod_def, so imports should go there # - But if you change this to decl_write() instead of # def_write(), you end up 'using error::e_usage' in say # 'assign_osh', and it hasn't been defined yet. if alias: # using runtime_asdl::emit_e = EMIT; self.def_write_ind('using %s = %s::%s;\n', alias, last_dotted, name) else: # from _devbuild.gen.id_kind_asdl import Id # -> using id_kind_asdl::Id. using_str = 'using %s::%s;\n' % (last_dotted, name) self.def_write_ind(using_str) # Fully qualified: # self.def_write_ind('using %s::%s;\n', '::'.join(dotted_parts), name) # Hack for default args. Without this limitation, we write # 'using' of names that aren't declared yet. # suffix_op is needed for string_ops.py, for some reason if (name in ('Id', 'scope_e', 'lex_mode_e', 'suffix_op', 'sh_lvalue', 'part_value', 'loc', 'word', 'word_part', 'cmd_value', 'hnode')): self.decl_write(using_str) else: # If we're importing a module without an alias, we don't need to do # anything. 'namespace cmd_eval' is already defined. if not alias: return # from asdl import format as fmt # -> namespace fmt = format; self.def_write_ind('namespace %s = %s;\n', alias, name) def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> T: pass # Statements def visit_block(self, block: 'mypy.nodes.Block') -> T: self.def_write('{\n') # not indented to use same line as while/if self.indent += 1 if self.prepend_to_block: # TODO: put the pointers first, and then register a single # StackRoots record. done = set() for lval_name, c_type, is_param in self.prepend_to_block: if not is_param and lval_name not in done: if util.SMALL_STR and c_type == 'Str': self.def_write_ind('%s %s(nullptr);\n', c_type, lval_name) else: rhs = ' = nullptr' if CTypeIsManaged(c_type) else '' self.def_write_ind('%s %s%s;\n', c_type, lval_name, rhs) done.add(lval_name) # Figure out if we have any roots to write with StackRoots roots = [] # keep it sorted for lval_name, c_type, is_param in self.prepend_to_block: #self.log('%s %s %s', lval_name, c_type, is_param) if lval_name not in roots and CTypeIsManaged(c_type): roots.append(lval_name) #self.log('roots %s', roots) if len(roots): if (self.stack_roots_warn and len(roots) > self.stack_roots_warn): log('WARNING: %s() has %d stack roots. Consider refactoring this function.' % (self.current_func_node.fullname, len(roots))) for i, r in enumerate(roots): self.def_write_ind('StackRoot _root%d(&%s);\n' % (i, r)) self.def_write('\n') self.prepend_to_block = None self._write_body(block.body) self.indent -= 1 self.def_write_ind('}\n') def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> T: # TODO: Avoid writing docstrings. # If it's just a string, then we don't need it. self.def_write_ind('') self.accept(o.expr) self.def_write(';\n') def visit_operator_assignment_stmt( self, o: 'mypy.nodes.OperatorAssignmentStmt') -> T: self.def_write_ind('') self.accept(o.lvalue) self.def_write(' %s= ', o.op) # + to += self.accept(o.rvalue) self.def_write(';\n') def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> T: self.def_write_ind('while (') self.accept(o.expr) self.def_write(') ') self.accept(o.body) def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> T: # Examples: # return # return None # return my_int + 3; self.def_write_ind('return ') if o.expr: if not (isinstance(o.expr, NameExpr) and o.expr.name == 'None'): # Note: the type of the return expression (self.types[o.expr]) # and the return type of the FUNCTION are different. Use the # latter. ret_type = self.current_func_node.type.ret_type c_ret_type, returning_tuple, _ = GetCReturnType(ret_type) # return '', None # tuple literal # but NOT # return tuple_func() if returning_tuple and isinstance(o.expr, TupleExpr): self.def_write('%s(' % c_ret_type) for i, item in enumerate(o.expr.items): if i != 0: self.def_write(', ') self.accept(item) self.def_write(');\n') return # Not returning tuple self.accept(o.expr) self.def_write(';\n') def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> T: pass def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> T: # Not sure why this wouldn't be true assert len(o.expr) == 1, o.expr cond = o.expr[0] if not _CheckCondition(cond, self.types): self.report_error( o, "Use explicit len(obj) or 'obj is not None' for mystr, mylist, mydict" ) return if util.MaybeSkipIfStmt(self, o): return self.def_write_ind('if (') for e in o.expr: self.accept(e) self.def_write(') ') for node in o.body: self.accept(node) if o.else_body: self.def_write_ind('else ') self.accept(o.else_body) def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> T: self.def_write_ind('break;\n') def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> T: self.def_write_ind('continue;\n') def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> T: self.def_write_ind('; // pass\n') def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> T: # C++ compiler is aware of assert(0) for unreachable code if o.expr and isinstance(o.expr, CallExpr): if o.expr.callee.name == 'AssertionError': self.def_write_ind('assert(0); // AssertionError\n') return if o.expr.callee.name == 'NotImplementedError': self.def_write_ind( 'FAIL(kNotImplemented); // Python NotImplementedError\n') return self.def_write_ind('throw ') # it could be raise -> throw ; . OSH uses that. if o.expr: self.accept(o.expr) self.def_write(';\n') def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> T: self.def_write_ind('try ') self.accept(o.body) caught = False for t, v, handler in zip(o.types, o.vars, o.handlers): c_type = None if isinstance(t, NameExpr): if t.name in ('IOError', 'OSError'): self.report_error( handler, 'Use except (IOError, OSError) rather than catching just one' ) c_type = '%s*' % t.name elif isinstance(t, MemberExpr): # Heuristic c_type = '%s::%s*' % (t.expr.name, t.name) elif isinstance(t, TupleExpr): if len(t.items) == 2: e1 = t.items[0] e2 = t.items[1] if isinstance(e1, NameExpr) and isinstance(e2, NameExpr): names = [e1.name, e2.name] names.sort() if names == ['IOError', 'OSError']: c_type = 'IOError_OSError*' # Base class in mylib else: raise AssertionError() if c_type is None: c_type = 'INVALID_TRY_EXCEPT' # Causes compile error if v: self.def_write_ind('catch (%s %s) ', c_type, v.name) else: self.def_write_ind('catch (%s) ', c_type) self.accept(handler) caught = True # DUMMY to prevent compile errors # TODO: Remove this if not caught: self.def_write_ind('catch (std::exception const&) { }\n') if o.else_body: self.report_error(o, 'try/else not supported') if o.finally_body: self.report_error(o, 'try/finally not supported') def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> T: self.report_error( o, 'File should start with "from __future__ import print_function"') def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> T: self.report_error(o, 'exec not allowed')