10000 py/emitcommon: Don't implicitly close class vars that are assigned to. · sstobbe/micropython@590de39 · GitHub
[go: up one dir, main page]

Skip to content

Commit 590de39

Browse files
committed
py/emitcommon: Don't implicitly close class vars that are assigned to.
When in a class body or at the module level don't implicitly close over variables that have been assigned to. Fixes issue micropython#8603. Signed-off-by: Damien George <damien@micropython.org>
1 parent a21fd7c commit 590de39

File tree

3 files changed

+87
-4
lines changed

3 files changed

+87
-4
lines changed

py/emitcommon.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,14 @@ qstr_short_t mp_emit_common_use_qstr(mp_emit_common_t *emit, qstr qst) {
4444
void mp_emit_common_get_id_for_modification(scope_t *scope, qstr qst) {
4545
// name adding/lookup
4646
id_info_t *id = scope_find_or_add_id(scope, q 10000 st, ID_INFO_KIND_GLOBAL_IMPLICIT);
47-
if (SCOPE_IS_FUNC_LIKE(scope->kind) && id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) {
48-
// rebind as a local variable
49-
id->kind = ID_INFO_KIND_LOCAL;
47+
if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) {
48+
if (SCOPE_IS_FUNC_LIKE(scope->kind)) {
49+
// rebind as a local variable
50+
id->kind = ID_INFO_KIND_LOCAL;
51+
} else {
52+
// mark this as assigned, to prevent it from being closed over
53+
id->kind = ID_INFO_KIND_GLOBAL_IMPLICIT_ASSIGNED;
54+
}
5055
}
5156
}
5257

@@ -57,7 +62,7 @@ void mp_emit_common_id_op(emit_t *emit, const mp_emit_method_table_id_ops_t *emi
5762
assert(id != NULL);
5863

5964
// call the emit backend with the correct code
60-
if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) {
65+
if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT || id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT_ASSIGNED) {
6166
emit_method_table->global(emit, qst, MP_EMIT_IDOP_GLOBAL_NAME);
6267
} else if (id->kind == ID_INFO_KIND_GLOBAL_EXPLICIT) {
6368
emit_method_table->global(emit, qst, MP_EMIT_IDOP_GLOBAL_GLOBAL);

py/scope.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
typedef enum {
3333
ID_INFO_KIND_UNDECIDED,
3434
ID_INFO_KIND_GLOBAL_IMPLICIT,
35+
ID_INFO_KIND_GLOBAL_IMPLICIT_ASSIGNED,
3536
ID_INFO_KIND_GLOBAL_EXPLICIT,
3637
ID_INFO_KIND_LOCAL, // in a function f, written and only referenced by f
3738
ID_INFO_KIND_CELL, // in a function f, read/written by children of f

tests/basics/scope_class.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# test scoping rules that involve a class
2+
3+
# the inner A.method should be independent to the local function called method
4+
def test1():
5+
def method():
6+
pass
7+
8+
class A:
9+
def method():
10+
pass
11+
12+
print(hasattr(A, "method"))
13+
print(hasattr(A(), "method"))
14+
15+
16+
test1()
17+
18+
19+
# the inner A.method is a closure and overrides the local function called method
20+
def test2():
21+
def method():
22+
return "outer"
23+
24+
class A:
25+
nonlocal method
26+
27+
def method():
28+
return "inner"
29+
30+
print(hasattr(A, "method"))
31+
print(hasattr(A(), "method"))
32+
return method() # this is actually A.method
33+
34+
35+
print(test2())
36+
37+
38+
# a class body will capture external variables by value (not by reference)
39+
def test3(x):
40+
class A:
41+
local = x
42+
43+
x += 1
44+
return x, A.local
45+
46+
47+
print(test3(42))
48+
49+
50+
# assigning to a variable in a class will implicitly prevent it from closing over a variable
51+
def test4(global_):
52+
class A:
53+
local = global_ # fetches outer global_
54+
global_ = "global2" # creates class attribute
55+
56+
global_ += 1 # updates local variable
57+
return global_, A.local, A.global_
58+
59+
60+
global_ = "global"
61+
print(test4(42), global_)
62+
63+
64+
# methods within a class can close over variables outside the class
65+
def test5(x):
66+
def closure():
67+
return x
68+
69+
class A:
70+
def method():
71+
return x, closure()
72+
73+
closure = lambda: x + 1 # change it after A has been created
74+
return A
75+
76+
77+
print(test5(42).method())

0 commit comments

Comments
 (0)
0