Skip to content

Commit 75e4750

Browse files
gh-98: Bind SELF in map-indexed calls.
1 parent 61ad9a8 commit 75e4750

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

docs/SPECIFICATION.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@
338338
339339
Intermediate nested maps MUST be created on-demand when assigning to deeper keys. Assigning a value of a different type to an existing key MUST raise a runtime error.
340340
341+
When a `MAP` index expression is used in call position and the indexed value resolves to a `FUNC`, the interpreter MUST bind `SELF` for the duration of that call to the `MAP` value that supplied the function. If the `MAP` expression refers to a visible pointer alias to a `MAP` binding, `SELF` MUST alias the underlying binding so that mutations through `SELF` update the original map. Otherwise `SELF` MUST follow normal `MAP` value semantics, so calling through a non-pointer value mutates only the local copy.
342+
341343
---
342344
343345
#### 4.5.3 Boolean representation

src/interpreter.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,89 @@ static void trace_log_step(Interpreter* interp, Stmt* stmt, Env* env) {
114114
interp->trace_next_step_index++;
115115
}
116116

117+
static bool bind_self_for_map_call(Interpreter* interp, Expr* target_expr, Env* caller_env, Env* call_env, int line, int col) {
118+
if (!interp || !call_env || !target_expr) return true;
119+
120+
// SELF should mirror the immediate map target of the call site.
121+
Expr* base_expr = target_expr;
122+
if (!base_expr) return true;
123+
124+
if (base_expr->type == EXPR_IDENT && base_expr->as.ident) {
125+
EnvEntry* raw_entry = env_get_entry(caller_env, base_expr->as.ident);
126+
Value base_val = value_null();
127+
bool initialized = false;
128+
129+
if (!env_get(caller_env, base_expr->as.ident, &base_val, NULL, &initialized) || !initialized) {
130+
return true;
131+
}
132+
133+
if (raw_entry && raw_entry->alias_target && base_val.type == VAL_MAP) {
134+
env_define(call_env, "SELF", TYPE_MAP);
135+
if (!env_set_alias_cross(call_env,
136+
"SELF",
137+
raw_entry->alias_target_env ? raw_entry->alias_target_env : caller_env,
138+
raw_entry->alias_target,
139+
TYPE_MAP,
140+
true)) {
141+
value_free(base_val);
142+
if (interp->error) {
143+
free(interp->error);
144+
interp->error = NULL;
145+
}
146+
interp->error = strdup("Cannot bind SELF for map call");
147+
interp->error_line = line;
148+
interp->error_col = col;
149+
return false;
150+
}
151+
value_free(base_val);
152+
return true;
153+
}
154+
155+
if (base_val.type != VAL_MAP) {
156+
value_free(base_val);
157+
return true;
158+
}
159+
160+
env_define(call_env, "SELF", TYPE_MAP);
161+
if (!env_assign(call_env, "SELF", base_val, TYPE_MAP, true)) {
162+
value_free(base_val);
163+
if (interp->error) {
164+
free(interp->error);
165+
interp->error = NULL;
166+
}
167+
interp->error = strdup("Cannot bind SELF for map call");
168+
interp->error_line = line;
169+
interp->error_col = col;
170+
return false;
171+
}
172+
value_free(base_val);
173+
return true;
174+
}
175+
176+
Value base_val = eval_expr(interp, base_expr, caller_env);
177+
if (interp->error) return false;
178+
if (base_val.type != VAL_MAP) {
179+
value_free(base_val);
180+
return true;
181+
}
182+
183+
env_define(call_env, "SELF", TYPE_MAP);
184+
if (!env_assign(call_env, "SELF", base_val, TYPE_MAP, true)) {
185+
value_free(base_val);
186+
if (interp->error) {
187+
free(interp->error);
188+
interp->error = NULL;
189+
}
190+
interp->error = strdup("Cannot bind SELF for map call");
191+
interp->error_line = line;
192+
interp->error_col = col;
193+
return false;
194+
}
195+
196+
value_free(base_val);
197+
return true;
198+
}
199+
117200
static void trace_append(char** dst, size_t* len, size_t* cap, const char* text) {
118201
if (!dst || !len || !cap || !text) return;
119202
size_t add = strlen(text);
@@ -1480,6 +1563,18 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
14801563
if (pos_vals) free(pos_vals);
14811564
if (kw_vals) free(kw_vals);
14821565
if (kw_used) free(kw_used);
1566+
1567+
if (expr->as.call.callee->type == EXPR_INDEX && expr->as.call.callee->as.index.is_map) {
1568+
if (!bind_self_for_map_call(interp,
1569+
expr->as.call.callee->as.index.target,
1570+
env,
1571+
call_env,
1572+
expr->line,
1573+
expr->column)) {
1574+
env_free(call_env);
1575+
return value_null();
1576+
}
1577+
}
14831578

14841579
// Execute function body
14851580
if (trace_push_frame(interp,

tests/test2.pre

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,20 @@ ASSERT(EQ(m3<"b">, 0d1))
232232
ASSERT(EQ(m3, m3<"c">))
233233
DEL(m3)
234234

235+
! Regression test for GH-98: SELF in map-indexed calls
236+
MAP: method_map = <"n" = 0d1, "inc" = LAMBDA INT: (){ SELF<"n"> = ADD(SELF<"n">, 0d1); RETURN(SELF<"n">) }>
237+
MAP: method_copy = method_map
238+
MAP: method_alias = @method_map
239+
ASSERT(EQ(method_copy<"inc">(), 0d2))
240+
ASSERT(EQ(method_copy<"n">, 0d1))
241+
ASSERT(EQ(method_map<"n">, 0d1))
242+
ASSERT(EQ(method_alias<"inc">(), 0d2))
243+
ASSERT(EQ(method_map<"n">, 0d2))
244+
ASSERT(EQ(method_copy<"n">, 0d1))
245+
DEL(method_copy)
246+
DEL(method_alias)
247+
DEL(method_map)
248+
235249
! Pointer tests
236250
PRINT("Testing pointers...")
237251
INT: x = 0d1

0 commit comments

Comments
 (0)