From 038ce9a673d895ddbabd63c0a26ea231f05cbd01 Mon Sep 17 00:00:00 2001 From: Andy C Date: Sun, 28 Nov 2021 20:12:33 -0500 Subject: [PATCH] [mycpp] Only generate field names from __init__ (or Reset) This fixes a bug where the translation of process::Process and process::Job had duplicate this->state fields. - Add test exposes it in examples/classes.py - Change OSH source code to be explicit about fields in a few places. Note: Reset is a bit of hack for WordParser This translates and compiles now. Let's see how it does on the tests. --- core/state.py | 2 ++ mycpp/cppgen_pass.py | 42 ++++++++++++++++++++++++++++++--------- mycpp/examples/classes.py | 14 +++++++++++++ osh/builtin_printf.py | 12 ++++++----- 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/core/state.py b/core/state.py index fa98df74b6..70ad0325d5 100644 --- a/core/state.py +++ b/core/state.py @@ -1014,6 +1014,8 @@ def __init__(self, dollar0, argv, arena, debug_stack): # CALL_SOURCE, and BASH_LINENO. self.debug_stack = debug_stack + self.pwd = None # type: Optional[str] + self.current_spid = runtime.NO_SPID self.line_num = value.Str('') diff --git a/mycpp/cppgen_pass.py b/mycpp/cppgen_pass.py index 96a4ea55c9..e4e4b376c7 100644 --- a/mycpp/cppgen_pass.py +++ b/mycpp/cppgen_pass.py @@ -283,6 +283,7 @@ def __init__(self, types: Dict[Expression, Type], const_lookup, f, # This is all in the 'decl' phase. self.member_vars = {} # type: Dict[str, Type] self.current_class_name = None # for prototypes + self.current_method_name = None self.imported_names = set() # For module::Foo() vs. self.foo @@ -1418,11 +1419,19 @@ def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: self.accept(o.rvalue) self.write(';\n') - # Collect statements that look like self.foo = 1 - if isinstance(lval.expr, NameExpr) and lval.expr.name == 'self': - #log(' lval.name %s', lval.name) - lval_type = self.types[lval] - self.member_vars[lval.name] = lval_type + 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 calss 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] + self.member_vars[lval.name] = lval_type elif isinstance(lval, IndexExpr): # a[x] = 1 # d->set(x, 1) for both List and Dict @@ -2111,23 +2120,37 @@ def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: # Constructor is named after class if isinstance(stmt, FuncDef): - if stmt.name() == '__init__': + method_name = stmt.name() + if method_name == '__init__': self.decl_write_ind('%s(', o.name) self._WriteFuncParams(stmt.type.arg_types, stmt.arguments) self.decl_write(');\n') - # Must visit these for member vars! + # Visit for member vars + self.current_method_name = method_name self.accept(stmt.body) + self.current_method_name = None continue - if stmt.name() == '__enter__': + if method_name == '__enter__': continue - if stmt.name() == '__exit__': + if method_name == '__exit__': # Turn it into a destructor with NO ARGS self.decl_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 + + # Do we need this? I think everything under a class is a method? self.accept(stmt) self.current_class_name = None @@ -2149,6 +2172,7 @@ def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: self.current_class_name = o.name + # # Now we're visiting for definitions (not declarations). # block = o.defs diff --git a/mycpp/examples/classes.py b/mycpp/examples/classes.py index 5104536d74..11e7d418f7 100755 --- a/mycpp/examples/classes.py +++ b/mycpp/examples/classes.py @@ -39,6 +39,17 @@ def __init__(self, f): # Note: translated into an initializer list. ColorOutput.__init__(self, f) print('TextOutput constructor') + self.i = 0 # field only in derived class + + def MutateField(self): + # type: () -> None + self.num_chars = 42 + self.i = 43 + + def PrintFields(self): + # type: () -> None + print("num_chars = %d" % self.num_chars) # field from base + print("i = %d" % self.i) # field from derived class Base(object): @@ -90,6 +101,9 @@ def run_tests(): out.write('bar\n') log('Wrote %d bytes', out.num_chars) + out.MutateField() + out.PrintFields() + #b = Base() d = Derived() #log(b.method()) diff --git a/osh/builtin_printf.py b/osh/builtin_printf.py index 552282cb53..d674b12fd5 100755 --- a/osh/builtin_printf.py +++ b/osh/builtin_printf.py @@ -7,7 +7,7 @@ import time as time_ # avoid name conflict from _devbuild.gen import arg_types -from _devbuild.gen.id_kind_asdl import Id, Kind +from _devbuild.gen.id_kind_asdl import Id, Kind, Id_t, Kind_t from _devbuild.gen.runtime_asdl import ( cmd_value__Argv, value_e, value__Str, value ) @@ -61,12 +61,14 @@ def __init__(self, lexer): # type: (Lexer) -> None self.lexer = lexer + # uninitialized values + self.cur_token = None # type: Token + self.token_type = Id.Undefined_Tok # type: Id_t + self.token_kind = Kind.Undefined # type: Kind_t + def _Next(self, lex_mode): # type: (lex_mode_t) -> None - """Set the next lex state, but don't actually read a token. - - We need this for proper interactive parsing. - """ + """Advance a token.""" self.cur_token = self.lexer.Read(lex_mode) self.token_type = self.cur_token.id self.token_kind = consts.GetKind(self.token_type)