///|
fn Interpreter::destructuring_property_key(
  self : Interpreter,
  ctx : ExecContext,
  prop : @ast.PropPat,
  env : Environment,
) -> Value raise Error {
  match prop.computed_key {
    Some(key_expr) => {
      let key_val = self.eval_expr(ctx, key_expr, env)
      to_property_key(key_val, interp=Some(self))
    }
    None => String_(prop.key)
  }
}

///|
fn destructuring_mark_used_key(
  key : Value,
  string_keys : @set.Set[String],
  symbol_keys : Map[Int, Bool],
) -> Unit {
  match key {
    String_(s) => string_keys.add(s)
    Symbol(sym) => symbol_keys[sym.id] = true
    _ => ()
  }
}

///|
fn Interpreter::bind_pattern(
  self : Interpreter,
  pattern : @ast.Pattern,
  value : Value,
  env : Environment,
  bk : BindingKind,
  ctx~ : ExecContext,
) -> Unit raise Error {
  match pattern {
    IdentPat(name) =>
      if bk == VarBinding && env.bindings.contains(name) {
        env.assign(name, value)
      } else if (bk == LetBinding || bk == ConstBinding) &&
        env.bindings.contains(name) {
        // let/const hoisted with TDZ - initialize it
        env.initialize(name, value)
      } else {
        env.def(name, value, bk)
      }
    DefaultPat(inner_pat, default_expr) => {
      let final_val : Value = if value is Undefined {
        match inner_pat {
          IdentPat(name) => self.eval_named_expr(ctx, default_expr, env, name)
          _ => self.eval_expr(ctx, default_expr, env)
        }
      } else {
        value
      }
      self.bind_pattern(inner_pat, final_val, env, bk, ctx~)
    }
    AssignTarget(expr) =>
      // Member expression targets are only valid in assignment destructuring,
      // not in binding declarations (let/const/var). Delegate to assign_to_expr.
      self.assign_to_expr(ctx, expr, value, env)
    ArrayPat(elements, rest) => {
      // Per spec, destructuring null/undefined must throw TypeError
      if value is (Null | Undefined) {
        raise @errors.TypeError(
          message=value.to_string() + " is not iterable (cannot destructure)",
        )
      }
      // Always use iterator protocol per spec (ArrayBindingPattern uses GetIterator)
      let loc_dstr = @token.Loc::default()
      // On generator resume with a stacked dstr iterator: abrupt resumes close
      // and signal; normal (NextAction) resumes drain stale entries without
      // calling return() — the replay creates fresh iterators.
      match ctx.current_generator {
        Some(g) if g.dstr_iterator_stack.length() > 0 =>
          match g.resume_action {
            ReturnAction(_) | ThrowAction(_) =>
              self.close_dstr_stack_on_abrupt_resume(g, loc_dstr)
            NextAction =>
              while g.dstr_iterator_stack.length() > 0 {
                let _ = g.dstr_iterator_stack.pop()
              }
          }
        _ => ()
      }
      let iterator_sym_dstr = self.realm_state.well_known_symbols.iterator
      let iterator_method_dstr = self.get_computed_property(
        value,
        Symbol(iterator_sym_dstr),
        loc_dstr,
      )
      let iterator : Value = match iterator_method_dstr {
        Object(data) =>
          match data.callable {
            Some(_) =>
              self.call_value(iterator_method_dstr, value, [], loc_dstr)
            None =>
              raise @errors.TypeError(
                message=value.to_string() +
                  " is not iterable (cannot destructure)",
              )
          }
        _ =>
          raise @errors.TypeError(
            message=value.to_string() + " is not iterable (cannot destructure)",
          )
      }
      // Per spec §7.4.1 GetIterator: next method lookup is part of iterator record
      // construction; its getter is observable even for empty patterns.
      let next_method = self.get_iterator_next_method(iterator, loc_dstr)
      // Per spec: ArrayBindingPattern : [ ] still requires GetIterator + IteratorClose
      if elements.length() == 0 && rest is None {
        self.iterator_close(iterator, loc_dstr)
        return
      }
      // Lazily consume iterator values per spec — one next() call per element
      let mut iter_done = false
      for i = 0; i < elements.length(); i = i + 1 {
        // Advance iterator one step for this element position. Elisions only
        // perform IteratorStep; they must not perform IteratorValue.
        match elements[i] {
          Some(pat) => {
            let val : Value = if iter_done {
              Undefined
            } else {
              match self.iterator_step_result(iterator, next_method, loc_dstr) {
                None => {
                  iter_done = true
                  Undefined
                }
                Some(result) => self.iterator_result_value(result, loc_dstr)
              }
            }
            self.bind_pattern(pat, val, env, bk, ctx~) catch {
              e => {
                if e is YieldSignal(_) {
                  if !iter_done {
                    match ctx.current_generator {
                      Some(g) => g.dstr_iterator_stack.push(iterator)
                      None => ()
                    }
                  }
                } else if !iter_done {
                  self.iterator_close_throw(iterator, loc_dstr)
                }
                raise e
              }
            }
          }
          None =>
            if !iter_done {
              match self.iterator_step_result(iterator, next_method, loc_dstr) {
                None => iter_done = true
                Some(_) => ()
              }
            }
        }
      }
      // Handle rest pattern: collect remaining iterator values
      match rest {
        Some(rest_pat) => {
          let rest_elements : Array[Value] = []
          if iter_done {
            // iterator already exhausted, rest is empty
          } else {
            while true {
              match self.iterator_step_value(iterator, next_method, loc_dstr) {
                None => break
                Some(value) => rest_elements.push(value)
              }
            }
          }
          self.bind_pattern(rest_pat, make_array(rest_elements), env, bk, ctx~)
        }
        None =>
          // Per spec: IteratorClose — call iterator.return() if not exhausted
          if iter_done == false {
            self.iterator_close(iterator, loc_dstr)
          }
      }
    }
    ObjectPat(props, rest) => {
      // Per spec, destructuring null/undefined must throw TypeError
      if value is (Null | Undefined) {
        raise @errors.TypeError(
          message="Cannot destructure " + value.to_string() + " value",
        )
      }
      let used_string_keys = @set.Set::default()
      let used_symbol_keys : Map[Int, Bool] = Map([])
      for prop in props {
        let property_key = self.destructuring_property_key(ctx, prop, env)
        destructuring_mark_used_key(
          property_key, used_string_keys, used_symbol_keys,
        )
        match prop.value {
          IdentPat(name) =>
            if bk == VarBinding {
              // SingleNameBinding resolves the binding before GetV(value, P),
              // so object-environment HasBinding traps are observable before
              // the source getter or default initializer runs.
              let _ = env.has(name)
            }
          _ => ()
        }
        let val : Value = self.get_computed_property(
          value,
          property_key,
          @token.Loc::default(),
        )
        let final_val : Value = if val is Undefined {
          match prop.default_val {
            Some(default_expr) =>
              match prop.value {
                IdentPat(name) =>
                  self.eval_named_expr(ctx, default_expr, env, name)
                _ => self.eval_expr(ctx, default_expr, env)
              }
            None => Undefined
          }
        } else {
          val
        }
        self.bind_pattern(prop.value, final_val, env, bk, ctx~)
      }
      match rest {
        Some(rest_pat) => {
          let rest_props : Map[String, Value] = Map([])
          let rest_symbol_props : Map[Int, Value] = Map([])
          let loc_rest = @token.Loc::default()
          match value {
            Object(data) => {
              // CopyDataProperties: copy own enumerable properties, invoking getters
              // First collect all own enumerable string keys (from properties and descriptors)
              let all_keys = @set.Set::default()
              data.bag.properties.each(fn(k, _v) { all_keys.add(k) })
              data.bag.descriptors.each(fn(k, _v) { all_keys.add(k) })
              all_keys.each(fn(k) raise {
                if !used_string_keys.contains(k) {
                  let is_enumerable = match data.bag.descriptors.get(k) {
                    Some(desc) => desc.enumerable
                    None => true
                  }
                  if is_enumerable {
                    // Use get_property to invoke getters
                    rest_props[k] = self.get_property(value, k, loc_rest)
                  }
                }
              })
              // Also copy enumerable symbol properties
              data.bag.symbol_properties.each(fn(k, v) {
                let is_enumerable = match data.bag.symbol_descriptors.get(k) {
                  Some(desc) => desc.enumerable
                  None => true
                }
                if is_enumerable && !used_symbol_keys.contains(k) {
                  rest_symbol_props[k] = v
                }
              })
            }
            _ => ()
          }
          self.bind_pattern(
            rest_pat,
            Object({
              bag: {
                properties: rest_props,
                symbol_properties: rest_symbol_props,
                descriptors: Map([]),
                symbol_descriptors: Map([]),
                internal_slots: Map([]),
                host_slots: Map([]),
              },
              prototype: get_obj_proto(realm_state=Some(self.realm_state)),
              callable: None,
              class_name: "Object",
              extensible: true,
              arraybuffer_state: None,
            }),
            env,
            bk,
            ctx~,
          )
        }
        None => ()
      }
    }
  }
}

///|
fn Interpreter::assign_pattern(
  self : Interpreter,
  ctx : ExecContext,
  pattern : @ast.Pattern,
  value : Value,
  env : Environment,
) -> Unit raise Error {
  match pattern {
    IdentPat(name) =>
      env.assign_with_strict(name, value, ctx.strict) catch {
        @errors.ReferenceError(message~) =>
          if !ctx.strict && message == "\{name} is not defined" {
            // Sloppy mode: implicit global creation for unbound names only.
            // TDZ errors ("Cannot access before initialization") must propagate.
            self.global.def(name, value, VarBinding)
            self.mirror_to_global(name, value, configurable=true)
          } else {
            raise @errors.ReferenceError(message~)
          }
        other => raise other
      }
    DefaultPat(inner_pat, default_expr) => {
      let final_val : Value = if value is Undefined {
        match inner_pat {
          IdentPat(name) => self.eval_named_expr(ctx, default_expr, env, name)
          _ => self.eval_expr(ctx, default_expr, env)
        }
      } else {
        value
      }
      self.assign_pattern(ctx, inner_pat, final_val, env)
    }
    AssignTarget(expr) =>
      // Member expression assignment target: [obj.x] = [1]
      self.assign_to_expr(ctx, expr, value, env)
    ArrayPat(elements, rest) => {
      // Per spec, destructuring null/undefined must throw TypeError
      if value is (Null | Undefined) {
        raise @errors.TypeError(
          message=value.to_string() + " is not iterable (cannot destructure)",
        )
      }
      // Always use iterator protocol per spec (ArrayAssignmentPattern uses GetIterator)
      let loc_dstr = @token.Loc::default()
      // On generator resume with a stacked dstr iterator: abrupt resumes close
      // and signal; normal (NextAction) resumes drain stale entries without
      // calling return() — the replay creates fresh iterators.
      match ctx.current_generator {
        Some(g) if g.dstr_iterator_stack.length() > 0 =>
          match g.resume_action {
            ReturnAction(_) | ThrowAction(_) =>
              self.close_dstr_stack_on_abrupt_resume(g, loc_dstr)
            NextAction =>
              while g.dstr_iterator_stack.length() > 0 {
                let _ = g.dstr_iterator_stack.pop()
              }
          }
        _ => ()
      }
      let iterator_sym_dstr = self.realm_state.well_known_symbols.iterator
      let iterator_method_dstr = self.get_computed_property(
        value,
        Symbol(iterator_sym_dstr),
        loc_dstr,
      )
      let iterator_a : Value = match iterator_method_dstr {
        Object(data) =>
          match data.callable {
            Some(_) =>
              self.call_value(iterator_method_dstr, value, [], loc_dstr)
            None =>
              raise @errors.TypeError(
                message=value.to_string() +
                  " is not iterable (cannot destructure)",
              )
          }
        _ =>
          raise @errors.TypeError(
            message=value.to_string() + " is not iterable (cannot destructure)",
          )
      }
      // Per spec §7.4.1 GetIterator: next method lookup is part of iterator record
      // construction; its getter is observable even for empty patterns.
      let next_method_a = self.get_iterator_next_method(iterator_a, loc_dstr)
      // Per spec: ArrayAssignmentPattern : [ ] still requires GetIterator + IteratorClose
      if elements.length() == 0 && rest is None {
        self.iterator_close(iterator_a, loc_dstr)
        return
      }
      // Lazily consume iterator values per spec — one next() call per element.
      // For AssignTarget elements, lref is evaluated BEFORE the iterator step
      // per spec §13.1 (lref-before-IteratorStepValue ordering).
      let mut iter_done_a = false
      for i = 0; i < elements.length(); i = i + 1 {
        // Advance iterator one step for this element position. Elisions only
        // perform IteratorStep; they must not perform IteratorValue.
        match elements[i] {
          Some(pat) => {
            // For AssignTarget patterns, pre-evaluate the lref before consuming
            // from the iterator so that lref errors fire before next() is called.
            let assign_expr : @ast.Expr? = match pat {
              AssignTarget(expr) => Some(expr)
              DefaultPat(AssignTarget(expr), _) => Some(expr)
              _ => None
            }
            match assign_expr {
              Some(expr) => {
                // Phase 1: evaluate lref (may throw before any iterator call)
                let lref = self.eval_lref_of_expr(ctx, expr, env) catch {
                  e => {
                    if e is YieldSignal(_) {
                      if !iter_done_a {
                        match ctx.current_generator {
                          Some(g) => g.dstr_iterator_stack.push(iterator_a)
                          None => ()
                        }
                      }
                    } else if !iter_done_a {
                      self.iterator_close_throw(iterator_a, loc_dstr)
                    }
                    raise e
                  }
                }
                // Phase 2: consume one value from iterator
                let val : Value = if iter_done_a {
                  Undefined
                } else {
                  let step = self.iterator_step_result(
                    iterator_a, next_method_a, loc_dstr,
                  ) catch {
                    e => {
                      self.iterator_close_throw(iterator_a, loc_dstr)
                      raise e
                    }
                  }
                  match step {
                    None => {
                      iter_done_a = true
                      Undefined
                    }
                    Some(result) => self.iterator_result_value(result, loc_dstr)
                  }
                }
                // Phase 3: apply default initializer if element is undefined
                let final_val : Value = match pat {
                  DefaultPat(_, default_expr) =>
                    if val is Undefined {
                      self.eval_expr(ctx, default_expr, env) catch {
                        e => {
                          if e is YieldSignal(_) {
                            if !iter_done_a {
                              match ctx.current_generator {
                                Some(g) =>
                                  g.dstr_iterator_stack.push(iterator_a)
                                None => ()
                              }
                            }
                          } else if !iter_done_a {
                            self.iterator_close_throw(iterator_a, loc_dstr)
                          }
                          raise e
                        }
                      }
                    } else {
                      val
                    }
                  _ => val
                }
                // Phase 4: assign via the pre-evaluated lref
                self.put_lref(ctx, lref, final_val, env) catch {
                  e => {
                    if !iter_done_a && !(e is YieldSignal(_)) {
                      self.iterator_close_throw(iterator_a, loc_dstr)
                    }
                    raise e
                  }
                }
              }
              None => {
                // Non-AssignTarget: consume from iterator, then bind pattern
                let val : Value = if iter_done_a {
                  Undefined
                } else {
                  match
                    self.iterator_step_result(
                      iterator_a, next_method_a, loc_dstr,
                    ) {
                    None => {
                      iter_done_a = true
                      Undefined
                    }
                    Some(result) => self.iterator_result_value(result, loc_dstr)
                  }
                }
                self.assign_pattern(ctx, pat, val, env) catch {
                  e => {
                    if e is YieldSignal(_) {
                      if !iter_done_a {
                        match ctx.current_generator {
                          Some(g) => g.dstr_iterator_stack.push(iterator_a)
                          None => ()
                        }
                      }
                    } else if !iter_done_a {
                      self.iterator_close_throw(iterator_a, loc_dstr)
                    }
                    raise e
                  }
                }
              }
            }
          }
          None =>
            if !iter_done_a {
              match
                self.iterator_step_result(iterator_a, next_method_a, loc_dstr) {
                None => iter_done_a = true
                Some(_) => ()
              }
            }
        }
      }
      // Handle rest pattern: collect remaining iterator values.
      // For AssignTarget rest, lref is evaluated BEFORE consuming rest values.
      match rest {
        Some(AssignTarget(expr)) => {
          // Pre-evaluate lref before consuming rest values from iterator
          let lref = self.eval_lref_of_expr(ctx, expr, env) catch {
            e => {
              if e is YieldSignal(_) {
                if !iter_done_a {
                  match ctx.current_generator {
                    Some(g) => g.dstr_iterator_stack.push(iterator_a)
                    None => ()
                  }
                }
              } else if !iter_done_a {
                self.iterator_close_throw(iterator_a, loc_dstr)
              }
              raise e
            }
          }
          let rest_elements : Array[Value] = []
          if !iter_done_a {
            while true {
              let step = self.iterator_step_value(
                iterator_a, next_method_a, loc_dstr,
              ) catch {
                e => {
                  self.iterator_close_throw(iterator_a, loc_dstr)
                  raise e
                }
              }
              match step {
                None => {
                  iter_done_a = true
                  break
                }
                Some(v) => rest_elements.push(v)
              }
            }
          }
          self.put_lref(ctx, lref, make_array(rest_elements), env) catch {
            e => {
              if !iter_done_a && !(e is YieldSignal(_)) {
                self.iterator_close_throw(iterator_a, loc_dstr)
              }
              raise e
            }
          }
        }
        Some(rest_pat) => {
          let rest_elements : Array[Value] = []
          if iter_done_a {
            // iterator already exhausted, rest is empty
          } else {
            while true {
              match
                self.iterator_step_value(iterator_a, next_method_a, loc_dstr) {
                None => break
                Some(value) => rest_elements.push(value)
              }
            }
          }
          self.assign_pattern(ctx, rest_pat, make_array(rest_elements), env)
        }
        None =>
          // Per spec: IteratorClose — call iterator.return() if not exhausted
          if iter_done_a == false {
            self.iterator_close(iterator_a, loc_dstr)
          }
      }
    }
    ObjectPat(props, rest) => {
      // Per spec, destructuring null/undefined must throw TypeError
      if value is (Null | Undefined) {
        raise @errors.TypeError(
          message="Cannot destructure " + value.to_string() + " value",
        )
      }
      let used_string_keys = @set.Set::default()
      let used_symbol_keys : Map[Int, Bool] = Map([])
      for prop in props {
        let property_key = self.destructuring_property_key(ctx, prop, env)
        destructuring_mark_used_key(
          property_key, used_string_keys, used_symbol_keys,
        )
        let val : Value = self.get_computed_property(
          value,
          property_key,
          @token.Loc::default(),
        )
        let final_val : Value = if val is Undefined {
          match prop.default_val {
            Some(default_expr) =>
              match prop.value {
                IdentPat(name) =>
                  self.eval_named_expr(ctx, default_expr, env, name)
                _ => self.eval_expr(ctx, default_expr, env)
              }
            None => Undefined
          }
        } else {
          val
        }
        self.assign_pattern(ctx, prop.value, final_val, env)
      }
      match rest {
        Some(rest_pat) => {
          let rest_props : Map[String, Value] = Map([])
          let rest_symbol_props : Map[Int, Value] = Map([])
          let loc_rest_a = @token.Loc::default()
          // CopyDataProperties: [[OwnPropertyKeys]] → [[GetOwnProperty]] →
          // [[Get]] in observable order, including String exotic indices and
          // Proxy dispatch. The target is a fresh ordinary object, so building
          // its owned maps locally has no observable intermediate state.
          for key in self.own_property_keys(value) {
            let excluded = match key {
              String_(name) => used_string_keys.contains(name)
              Symbol(sym) => used_symbol_keys.contains(sym.id)
              _ => false
            }
            if excluded {
              continue
            }
            match self.get_own_property(value, key) {
              Some((desc, _)) if desc.enumerable => {
                let property_value = self.get_computed_property(
                  value, key, loc_rest_a,
                )
                match key {
                  String_(name) => rest_props[name] = property_value
                  Symbol(sym) => rest_symbol_props[sym.id] = property_value
                  _ => ()
                }
              }
              _ => ()
            }
          }
          self.assign_pattern(
            ctx,
            rest_pat,
            Object({
              bag: {
                properties: rest_props,
                symbol_properties: rest_symbol_props,
                descriptors: Map([]),
                symbol_descriptors: Map([]),
                internal_slots: Map([]),
                host_slots: Map([]),
              },
              prototype: get_obj_proto(realm_state=Some(self.realm_state)),
              callable: None,
              class_name: "Object",
              extensible: true,
              arraybuffer_state: None,
            }),
            env,
          )
        }
        None => ()
      }
    }
  }
}

///|
pub fn Interpreter::eval_destructure_assign(
  self : Interpreter,
  ctx : ExecContext,
  pattern : @ast.Pattern,
  value : Value,
  env : Environment,
) -> Value raise Error {
  self.assign_pattern(ctx, pattern, value, env)
  value
}

///|
fn Interpreter::assign_to_expr(
  self : Interpreter,
  ctx : ExecContext,
  expr : @ast.Expr,
  value : Value,
  env : Environment,
) -> Unit raise Error {
  match expr {
    Ident(name, _) =>
      env.assign_with_strict(name, value, ctx.strict) catch {
        @errors.ReferenceError(message~) =>
          if !ctx.strict && message == "\{name} is not defined" {
            // Sloppy mode: implicit global creation for unbound names only.
            // TDZ errors must propagate as ReferenceError.
            self.global.def(name, value, VarBinding)
            self.mirror_to_global(name, value, configurable=true)
          } else {
            raise @errors.ReferenceError(message~)
          }
        e => raise e
      }
    Member(obj_expr, prop, loc) => {
      let obj = self.eval_expr(ctx, obj_expr, env)
      let _ = self.set_property(obj, prop, value, loc, strict=ctx.strict)
    }
    ComputedMember(obj_expr, key_expr, loc) => {
      let obj = self.eval_expr(ctx, obj_expr, env)
      let key = self.eval_expr(ctx, key_expr, env)
      let _ = self.set_computed_property(
        obj,
        key,
        value,
        loc,
        strict=ctx.strict,
      )
    }
    Call(_, _, _) => {
      if ctx.strict {
        raise @errors.SyntaxError(
          message="Invalid left-hand side in for-in/for-of",
        )
      }
      let _ = self.eval_expr(ctx, expr, env)
      raise @errors.ReferenceError(
        message="Invalid left-hand side in for-in/for-of",
      )
    }
    Grouping(_, _) => {
      let inner = unwrap_groupings(expr)
      match inner {
        Call(_, _, _) => {
          if ctx.strict {
            raise @errors.SyntaxError(
              message="Invalid left-hand side in for-in/for-of",
            )
          }
          let _ = self.eval_expr(ctx, inner, env)
          raise @errors.ReferenceError(
            message="Invalid left-hand side in for-in/for-of",
          )
        }
        _ =>
          raise @errors.ReferenceError(
            message="Invalid left-hand side in for-in/for-of",
          )
      }
    }
    _ =>
      raise @errors.ReferenceError(
        message="Invalid left-hand side in for-in/for-of",
      )
  }
}

// A pre-evaluated assignment reference: the LHS components are resolved eagerly
// before consuming from the iterator, so that lref errors fire before any next() call.

///|
priv enum EvalLRef {
  IdentLRef(String)
  MemberLRef(Value, String, @token.Loc)
  ComputedLRef(Value, Value, @token.Loc)
}

///|
// Evaluate the left-hand side of an assignment target expression without assigning.
// Used to implement spec §13.1 lref-before-IteratorStep ordering in destructuring.
fn Interpreter::eval_lref_of_expr(
  self : Interpreter,
  ctx : ExecContext,
  expr : @ast.Expr,
  env : Environment,
) -> EvalLRef raise Error {
  match expr {
    Ident(name, _) => IdentLRef(name)
    Member(obj_expr, prop, loc) => {
      let obj = self.eval_expr(ctx, obj_expr, env)
      MemberLRef(obj, prop, loc)
    }
    ComputedMember(obj_expr, key_expr, loc) => {
      let obj = self.eval_expr(ctx, obj_expr, env)
      let key = self.eval_expr(ctx, key_expr, env)
      ComputedLRef(obj, key, loc)
    }
    _ =>
      raise @errors.ReferenceError(
        message="Invalid left-hand side in destructuring assignment",
      )
  }
}

///|
// Assign a value to a pre-evaluated assignment reference.
fn Interpreter::put_lref(
  self : Interpreter,
  ctx : ExecContext,
  lref : EvalLRef,
  value : Value,
  env : Environment,
) -> Unit raise Error {
  match lref {
    IdentLRef(name) =>
      env.assign_with_strict(name, value, ctx.strict) catch {
        @errors.ReferenceError(_) =>
          if !ctx.strict {
            self.global.def(name, value, VarBinding)
            self.mirror_to_global(name, value, configurable=true)
          } else {
            raise @errors.ReferenceError(message="\{name} is not defined")
          }
        e => raise e
      }
    MemberLRef(obj, prop, loc) => {
      let _ = self.set_property(obj, prop, value, loc, strict=ctx.strict)
    }
    ComputedLRef(obj, key, loc) => {
      let _ = self.set_computed_property(
        obj,
        key,
        value,
        loc,
        strict=ctx.strict,
      )
    }
  }
}