-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Background
The VM compiler now uses the in-place version of compound-assignment operators (e.g. |=, &=, ++=) when resolved_assign_operation is Binding::Resolved. This is necessary to preserve reference semantics — when let y = x; x |= rhs, both x and y should see the updated map because they share an underlying Rc.
The problem
The interpreter handles OpAssignment by trying both the in-place op and the regular op at runtime, falling back on FunctionTypeMismatch:
let mut operations_to_try = [
(resolved_assign_operation, true), // in-place, try first
(resolved_operation, false), // regular, fallback
];
while let Some((op, in_place)) = operations_to_try.next() {
match func.call_checked(...) {
Err(FunctionTypeMismatch) => continue, // try next
...
}
}The VM has no equivalent fallback. The current workaround (Binding::Resolved check) avoids the worst cases (e.g. -= on integers where only the map's -= exists, returned as Binding::Dynamic from the broad fallback in resolve_function_binding), but it breaks down in cases where:
- The in-place op is
Binding::Dynamicbecause the static type isAnyat compile time, but the value is actually a map/list at runtime and should use in-place - More complex overload resolution scenarios
Desired fix
The VM compiler should emit bytecode that attempts the in-place call and, on type mismatch, falls back to the regular call + SetVar. This likely requires either:
- A new opcode (e.g.
CallInPlaceWithFallback) that encapsulates the try/fallback logic - Or a runtime flag/sentinel mechanism that allows conditional jumps after a "soft" call attempt
Related code
ndc_vm/src/compiler.rs—Expression::OpAssignmentmatch armndc_interpreter/src/evaluate/mod.rs—Expression::OpAssignmentmatch arm (reference implementation)ndc_interpreter/src/semantic/scope.rs—resolve_function_binding(the broad fallback in the third.or_else)
🤖 Generated with Claude Code