diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 355d1405..30561501 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -536,7 +536,7 @@ PK_API void py_clearexc(py_StackRef p0); #define AttributeError(self, n) \ py_exception(tp_AttributeError, "'%t' object has no attribute '%n'", (self)->type, (n)) #define UnboundLocalError(n) \ - py_exception(tp_UnboundLocalError, "local variable '%n' referenced before assignment", (n)) + py_exception(tp_UnboundLocalError, "cannot access local variable '%n' where it is not associated with a value", (n)) PK_API bool StopIteration() PY_RAISE; PK_API bool KeyError(py_Ref key) PY_RAISE; diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 9512a308..064de234 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -105,18 +105,16 @@ void NameExpr__emit_(Expr* self_, Ctx* ctx) { // we know this is a local variable Ctx__emit_(ctx, OP_LOAD_FAST, index, self->line); } else { - Opcode op; - // otherwise, if we are running dynamically, force `OP_LOAD_NAME` - if(ctx->co->src->is_dynamic) { - op = OP_LOAD_NAME; - // `OP_LOAD_NAME` won't handle `OP_LOAD_CLASS_GLOBAL` - // so `exec()` will raise an error for @property.setter - } else { - op = ctx->level <= 1 ? OP_LOAD_GLOBAL : OP_LOAD_NONLOCAL; - if(ctx->is_compiling_class && self->scope == NAME_GLOBAL) { - // if we are compiling a class, we should use `OP_LOAD_CLASS_GLOBAL` - // this is for @property.setter - op = OP_LOAD_CLASS_GLOBAL; + Opcode op = ctx->level <= 1 ? OP_LOAD_GLOBAL : OP_LOAD_NONLOCAL; + if(self->scope == NAME_GLOBAL) { + if(ctx->co->src->is_dynamic) { + op = OP_LOAD_NAME; + } else { + if(ctx->is_compiling_class) { + // if we are compiling a class, we should use `OP_LOAD_CLASS_GLOBAL` + // this is for @property.setter + op = OP_LOAD_CLASS_GLOBAL; + } } } Ctx__emit_(ctx, op, self->name, self->line); diff --git a/src/public/exec.c b/src/public/exec.c index 782dfe40..791a6d31 100644 --- a/src/public/exec.c +++ b/src/public/exec.c @@ -68,14 +68,23 @@ bool pk_execdyn(CodeObject* co, py_Ref module, py_Ref globals, py_Ref locals) { assert(module->type == tp_module); py_StackRef sp = vm->stack.sp; - assert(globals != NULL); + assert(globals != NULL && locals != NULL); + // check globals if(globals->type == tp_namedict) { globals = py_getslot(globals, 0); assert(globals->type == tp_module); } else { - assert(globals->type == tp_dict); + if(!py_istype(globals, tp_dict)) { return TypeError("globals must be a dict object"); } } + // check locals + switch(locals->type) { + case tp_locals: break; + case tp_dict: break; + case tp_nil: break; + default: return TypeError("locals must be a dict object"); + } + Frame* frame = Frame__new(co, sp, module, globals, locals, true); VM__push_frame(vm, frame); FrameResult res = VM__run_top_frame(vm); diff --git a/src/public/modules.c b/src/public/modules.c index 091b4fbc..fd90b3ad 100644 --- a/src/public/modules.c +++ b/src/public/modules.c @@ -554,11 +554,10 @@ static bool _builtins_execdyn(const char* title, int argc, py_Ref argv, enum py_ if(py_isnone(py_arg(1))) { py_newglobals(py_pushtmp()); } else { - if(!py_checktype(py_arg(1), tp_dict)) return false; py_push(py_arg(1)); } // locals - pk_push_special_locals(); + py_pushnil(); break; } case 3: { @@ -566,14 +565,12 @@ static bool _builtins_execdyn(const char* title, int argc, py_Ref argv, enum py_ if(py_isnone(py_arg(1))) { py_newglobals(py_pushtmp()); } else { - if(!py_checktype(py_arg(1), tp_dict)) return false; py_push(py_arg(1)); } // locals if(py_isnone(py_arg(2))) { - pk_push_special_locals(); + py_pushnil(); } else { - if(!py_checktype(py_arg(2), tp_dict)) return false; py_push(py_arg(2)); } break; diff --git a/tests/66_eval.py b/tests/66_eval.py index 54692830..4eaad666 100644 --- a/tests/66_eval.py +++ b/tests/66_eval.py @@ -31,12 +31,10 @@ def f(): ) assert b == 8 -class G: pass - def abc(): - g = G() - exec('a=1', g.__dict__) - return g.a + g = {} + exec('a=1', g) + return g['a'] res = abc() assert (res==1), res @@ -69,6 +67,11 @@ except NameError: pass # https://github.com/pocketpy/pocketpy/issues/339 -code = '\nprint(x)\ndef f():\n print(x)\nf()\n' +res = [] + +code = '\nres.append(x)\ndef f():\n res.append(x)\nf()\n' x = 33 -exec(code, {'x': 42}) \ No newline at end of file +exec(code, {'x': 42}) +assert res == [42, 42] +assert x == 33 + diff --git a/tests/67_locals_vs_globals.py b/tests/67_locals_vs_globals.py index 010a2a12..749837ea 100644 --- a/tests/67_locals_vs_globals.py +++ b/tests/67_locals_vs_globals.py @@ -32,11 +32,9 @@ assert "sys" not in globals() # With default locals: exec(""" import sys -assert locals() == globals() assert "sys" in locals() assert "sys" in globals() def main(): - assert locals() != globals() assert "sys" not in locals() # not the same locals as the outer scope assert "sys" in globals() # but now be can access `sys` via `globals()` main()