///|
fn Interpreter::eval_new(
  self : Interpreter,
  ctx : ExecContext,
  callee_expr : @ast.Expr,
  arg_exprs : Array[@ast.Expr],
  env : Environment,
  _loc : @token.Loc,
) -> Value raise Error {
  let ctor = self.eval_expr(ctx, callee_expr, env)
  let args : Array[Value] = self.eval_args_with_spread(ctx, arg_exprs, env)
  self.construct_value(ctor, args, _loc)
}

///|
fn is_object_like_for_constructor_return(value : Value) -> Bool {
  match value {
    Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) | Proxy(_) => true
    _ => false
  }
}

// Convert a constructor return value according to ES262 §9.2.2 step 13
// for class constructors. Derived constructors may only return object or
// undefined, while both base and derived constructors return `this` for the
// remaining case.

///|
fn apply_class_constructor_return(
  ctor_env : Environment,
  super_ctor : Value?,
  value : Value,
) -> Value raise Error {
  if is_object_like_for_constructor_return(value) {
    return value
  }
  match super_ctor {
    Some(_) =>
      match value {
        Undefined => ctor_env.get("this")
        _ =>
          raise @errors.TypeError(
            message="Derived constructors may only return object or undefined",
          )
      }
    None => ctor_env.get("this")
  }
}

///|
/// Install instance field initializers onto `this_arg`.
/// Used for base-class construction (before constructor body) and for
/// derived-class implicit constructors (after super() returns).
fn Interpreter::install_instance_fields(
  self : Interpreter,
  this_arg : Value,
  fields : Array[ClassFieldInit],
) -> Unit raise Error {
  // Class field initializers are always evaluated as strict code per spec
  let field_ctx : ExecContext = { strict: true, current_generator: None }
  let field_desc : PropDescriptor = {
    writable: true,
    enumerable: true,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  for field in fields {
    let field_value : Value = match field.initializer {
      Some(expr) => {
        let init_env = Environment::new(parent=Some(field.closure))
        init_env.def_builtin("this", this_arg)
        // Per ES262 §15.7 ClassFieldDefinitionEvaluation: a class field
        // initializer is its own function-like execution context. Bind
        // `` (always Undefined here — fields are invoked
        // implicitly during construction, never via `new`) so that direct
        // eval inside the field correctly classifies as "in function" and
        // permits `eval('new.target')` (matches V8). The
        // `[[InClassFieldInitializer]]` marker is consumed by perform_eval
        // (§19.2.1.1) to forbid `Contains arguments` in the eval source.
        init_env.def_builtin("", Undefined)
        init_env.def_builtin("[[InClassFieldInitializer]]", Bool(true))
        self.eval_expr(field_ctx, expr, init_env)
      }
      None => Undefined
    }
    match this_arg {
      Object(data) =>
        // CreateDataPropertyOrThrow semantics: check [[DefineOwnProperty]]
        // invariants before writing (non-extensible objects, non-configurable
        // existing properties must throw TypeError per spec).
        match field.key {
          Symbol(sym) => {
            if !data.bag.symbol_properties.contains(sym.id) && !data.extensible {
              raise @errors.TypeError(
                message="Cannot define class field on non-extensible object",
              )
            }
            match data.bag.symbol_descriptors.get(sym.id) {
              Some(d) =>
                if !d.configurable {
                  raise @errors.TypeError(
                    message="Cannot redefine non-configurable class field",
                  )
                }
              None => ()
            }
            data.bag.symbol_properties[sym.id] = field_value
            data.bag.symbol_descriptors[sym.id] = field_desc
          }
          String_(key_str) => {
            if !data.bag.properties.contains(key_str) && !data.extensible {
              raise @errors.TypeError(
                message="Cannot define class field on non-extensible object",
              )
            }
            match data.bag.descriptors.get(key_str) {
              Some(d) =>
                if !d.configurable {
                  raise @errors.TypeError(
                    message="Cannot redefine non-configurable class field",
                  )
                }
              None => ()
            }
            data.bag.properties[key_str] = field_value
            data.bag.descriptors[key_str] = field_desc
          }
          _ => {
            let key_str = to_js_string(field.key)
            if !data.bag.properties.contains(key_str) && !data.extensible {
              raise @errors.TypeError(
                message="Cannot define class field on non-extensible object",
              )
            }
            match data.bag.descriptors.get(key_str) {
              Some(d) =>
                if !d.configurable {
                  raise @errors.TypeError(
                    message="Cannot redefine non-configurable class field",
                  )
                }
              None => ()
            }
            data.bag.properties[key_str] = field_value
            data.bag.descriptors[key_str] = field_desc
          }
        }
      Proxy(proxy_data) => {
        // Use [[DefineOwnProperty]] semantics for Proxy: invoke the
        // "defineProperty" trap so the proxy observes definition rather than
        // [[Set]] (which would invoke the "set" trap instead).
        let trap = get_proxy_trap(proxy_data, "defineProperty", self)
        let target = get_proxy_target(proxy_data)
        let handler = get_proxy_handler(proxy_data)
        match trap {
          Some(trap_fn) => {
            let desc_obj = Object({
              bag: {
                properties: {
                  "value": field_value,
                  "writable": Bool(true),
                  "enumerable": Bool(true),
                  "configurable": Bool(true),
                },
                symbol_properties: Map([]),
                descriptors: Map([]),
                symbol_descriptors: Map([]),
                internal_slots: Map([]),
                host_slots: Map([]),
              },
              prototype: Null,
              callable: None,
              class_name: "Object",
              extensible: true,
              arraybuffer_state: None,
            })
            let trap_result = self.call_value(
              trap_fn,
              handler,
              [target, field.key, desc_obj],
              @token.Loc::default(),
            )
            // Per spec, if the trap returns a falsy value, throw TypeError
            if !is_truthy(trap_result) {
              raise @errors.TypeError(
                message="'defineProperty' on proxy: trap returned falsish for class field",
              )
            }
          }
          None => {
            // No defineProperty trap: define directly on the target
            let _ = self.set_computed_property(
              target,
              field.key,
              field_value,
              @token.Loc::default(),
              strict=true,
            )
          }
        }
      }
      Map(data) =>
        // CreateDataPropertyOrThrow on Map expando properties
        match field.key {
          Symbol(sym) => {
            data.bag.symbol_properties[sym.id] = field_value
            data.bag.symbol_descriptors[sym.id] = field_desc
          }
          String_(key_str) => {
            data.bag.properties[key_str] = field_value
            data.bag.descriptors[key_str] = field_desc
          }
          _ => {
            let key_str = to_js_string(field.key)
            data.bag.properties[key_str] = field_value
            data.bag.descriptors[key_str] = field_desc
          }
        }
      Set(data) =>
        // CreateDataPropertyOrThrow on Set expando properties
        match field.key {
          Symbol(sym) => {
            data.bag.symbol_properties[sym.id] = field_value
            data.bag.symbol_descriptors[sym.id] = field_desc
          }
          String_(key_str) => {
            data.bag.properties[key_str] = field_value
            data.bag.descriptors[key_str] = field_desc
          }
          _ => {
            let key_str = to_js_string(field.key)
            data.bag.properties[key_str] = field_value
            data.bag.descriptors[key_str] = field_desc
          }
        }
      _ => {
        // Array, Promise — set_computed_property has define-own semantics
        // for these (no prototype-chain setter lookup).
        let _ = self.set_computed_property(
          this_arg,
          field.key,
          field_value,
          @token.Loc::default(),
          strict=true,
        )
      }
    }
  }
}

///|
/// Initialize private instance fields during construction.
/// Each private field is stored in the instance's PrivateBrandStore internal slot:
///   obj.internal_slots[PrivateBrandStore]
///     → Object { bag.properties: { "brandId": Object { bag.properties: { "fieldName": value } } } }
/// The brand symbol's integer id is used as the key for the per-brand sub-storage.
fn Interpreter::install_private_fields(
  self : Interpreter,
  this_arg : Value,
  fields : Array[ClassFieldInit],
  brand : Value,
) -> Unit raise Error {
  // Always install the brand on the instance so that private method
  // brand checks work even without private fields.
  let data : ObjectData = match this_arg {
    Object(d) => d
    _ => return // non-Object exotic types can't have private names
  }
  let sym_id : Int = match brand {
    Symbol(s) => s.id
    _ => abort("brand must be a Symbol")
  }
  let brand_key : String = sym_id.to_string()
  // Ensure PrivateBrandStore slot exists
  let brand_store : Value = match
    data.bag.internal_slots.get(PrivateBrandStore) {
    Some(store) => store
    None => {
      let store = Object({
        bag: PropertyBag(),
        prototype: Null,
        callable: None,
        class_name: "Object",
        extensible: true,
        arraybuffer_state: None,
      })
      data.bag.internal_slots[PrivateBrandStore] = store
      store
    }
  }
  // Ensure per-brand sub-storage exists
  let brand_storage : ObjectData = match brand_store {
    Object(store_data) =>
      match store_data.bag.properties.get(brand_key) {
        Some(Object(inner)) => inner
        _ => {
          let inner = Object({
            bag: PropertyBag(),
            prototype: Null,
            callable: None,
            class_name: "Object",
            extensible: true,
            arraybuffer_state: None,
          })
          store_data.bag.properties[brand_key] = inner
          match inner {
            Object(d) => d
            _ => abort("brand storage must be Object")
          }
        }
      }
    _ => abort("PrivateBrandStore must be an Object")
  }
  // Evaluate and install each private field
  let field_ctx : ExecContext = { strict: true, current_generator: None }
  for field in fields {
    let field_value : Value = match field.initializer {
      Some(expr) => {
        let init_env = Environment::new(parent=Some(field.closure))
        init_env.def_builtin("this", this_arg)
        init_env.def_builtin("", Undefined)
        init_env.def_builtin("[[InClassFieldInitializer]]", Bool(true))
        self.eval_expr(field_ctx, expr, init_env)
      }
      None => Undefined
    }
    // For private fields, key is always String_(name)
    let field_name : String = match field.key {
      String_(s) => s
      _ => self.to_js_string(field.key)
    }
    brand_storage.bag.properties[field_name] = field_value
  }
}

///|
/// Read a private field value from an object.
/// Returns the field value on success, raises TypeError if brand check fails.
pub fn get_private_field(
  obj : Value,
  brand : Value,
  name : String,
) -> Value raise Error {
  let data : ObjectData = match obj {
    Object(d) => d
    _ =>
      raise @errors.TypeError(
        message="Cannot read private member from non-object",
      )
  }
  let sym_id : Int = match brand {
    Symbol(s) => s.id
    _ => abort("brand must be a Symbol")
  }
  let brand_key : String = sym_id.to_string()
  match data.bag.internal_slots.get(PrivateBrandStore) {
    Some(Object(store_data)) =>
      match store_data.bag.properties.get(brand_key) {
        Some(Object(inner)) =>
          match inner.bag.properties.get(name) {
            Some(v) => v
            None =>
              raise @errors.TypeError(
                message="Cannot read private member #\{name} from object",
              )
          }
        _ =>
          raise @errors.TypeError(
            message="Cannot read private member #\{name} from object",
          )
      }
    _ =>
      raise @errors.TypeError(
        message="Cannot read private member #\{name} from object",
      )
  }
}

///|
/// Write a private field value on an object.
/// Raises TypeError if brand check fails.
pub fn set_private_field(
  obj : Value,
  brand : Value,
  name : String,
  value : Value,
) -> Unit raise Error {
  let data : ObjectData = match obj {
    Object(d) => d
    _ =>
      raise @errors.TypeError(message="Cannot set private member on non-object")
  }
  let sym_id : Int = match brand {
    Symbol(s) => s.id
    _ => abort("brand must be a Symbol")
  }
  let brand_key : String = sym_id.to_string()
  match data.bag.internal_slots.get(PrivateBrandStore) {
    Some(Object(store_data)) =>
      match store_data.bag.properties.get(brand_key) {
        Some(Object(inner)) =>
          match inner.bag.properties.get(name) {
            Some(_) => inner.bag.properties[name] = value
            None =>
              raise @errors.TypeError(
                message="Cannot set private member #\{name} on object",
              )
          }
        _ =>
          raise @errors.TypeError(
            message="Cannot set private member #\{name} on object",
          )
      }
    _ =>
      raise @errors.TypeError(
        message="Cannot set private member #\{name} on object",
      )
  }
}

///|
/// Check if an object has been branded with the given private brand.
/// Returns false for non-object values (no TypeError).
pub fn has_brand(obj : Value, brand : Value) -> Bool {
  let data : ObjectData? = match obj {
    Object(d) => Some(d)
    _ => None
  }
  let sym_id : Int = match brand {
    Symbol(s) => s.id
    _ => abort("brand must be a Symbol")
  }
  let brand_key : String = sym_id.to_string()
  match data {
    Some(data) =>
      match data.bag.internal_slots.get(PrivateBrandStore) {
        Some(Object(store_data)) =>
          store_data.bag.properties.contains(brand_key)
        _ => false
      }
    None => false
  }
}

///|
/// Create a deferred instance-field initializer Value.
/// Stored as [[InitInstanceFields]] in the constructor env so the super()
/// call site can apply the fields to the actual `this` after super() returns.
fn make_instance_fields_init(
  fields : Array[ClassFieldInit],
  private_fields : Array[ClassFieldInit],
  private_brand : Value?,
  realm_state : RealmState,
) -> Value {
  stamp_function_realm(
    Object({
      bag: PropertyBag(),
      prototype: Null,
      callable: Some(
        InterpreterCallable("[[InitInstanceFields]]", fn(
          interp,
          this_arg,
          _,
        ) raise {
          interp.install_instance_fields(this_arg, fields)
          match private_brand {
            Some(brand) =>
              if private_fields.length() > 0 {
                interp.install_private_fields(this_arg, private_fields, brand)
              }
            None => ()
          }
          Undefined
        }),
      ),
      class_name: "Function",
      extensible: true,
      arraybuffer_state: None,
    }),
    realm_state=Some(realm_state),
  )
}

///|
fn make_constructor_instance(proto : Value, class_name : String) -> Value {
  Object({
    bag: PropertyBag(),
    prototype: proto,
    callable: None,
    class_name,
    extensible: true,
    arraybuffer_state: None,
  })
}

///|
pub fn Interpreter::get_prototype_from_constructor(
  self : Interpreter,
  ctor : Value,
  loc : @token.Loc,
) -> Value raise Error {
  let prototype = self.get_property(ctor, "prototype", loc)
  match prototype {
    Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) | Proxy(_) => prototype
    _ =>
      constructor_realm_intrinsic_prototype(
        ctor,
        "Object",
        get_obj_proto(realm_state=Some(self.realm_state)),
      )
  }
}

///|
pub fn Interpreter::define_simple_arguments_object(
  self : Interpreter,
  env : Environment,
  args : Array[Value],
  callee : Value,
  strict : Bool,
  params : Array[String],
) -> Unit raise Error {
  if params_include_arguments(params, None) {
    return
  }
  let tte : Value? = if env.has("[[ThrowTypeError]]") {
    Some(env.get("[[ThrowTypeError]]"))
  } else {
    None
  }
  env.def(
    "arguments",
    make_arguments_object(
      self.realm_state,
      self.realm_state.well_known_symbols,
      args,
      callee,
      strict,
      throw_type_error=tte,
      mapped_names=params,
      mapped_env=Some(env),
    ),
    VarBinding,
  )
}

///|
pub fn Interpreter::define_unmapped_arguments_object(
  self : Interpreter,
  env : Environment,
  args : Array[Value],
  callee : Value,
  strict : Bool,
) -> Unit raise Error {
  let tte : Value? = if env.has("[[ThrowTypeError]]") {
    Some(env.get("[[ThrowTypeError]]"))
  } else {
    None
  }
  env.def(
    "arguments",
    make_arguments_object(
      self.realm_state,
      self.realm_state.well_known_symbols,
      args,
      callee,
      strict,
      throw_type_error=tte,
    ),
    VarBinding,
  )
}

///|
fn arguments_iterator_value(realm_state : RealmState) -> Value {
  fn fallback() -> Value {
    make_method_func(name="values", length=0, realm_state=Some(realm_state), fn(
      this_val,
      _args,
    ) raise {
      realm_state.make_array_like_iterator_value(this_val)
    })
  }
  match realm_state.get_array_proto_values_intrinsic() {
    Some(value) => value
    None => fallback()
  }
}

///|
fn make_arguments_object(
  realm_state : RealmState,
  well_known_symbols : WellKnownSymbols,
  args : Array[Value],
  ctor : Value,
  strict : Bool,
  throw_type_error? : Value? = None,
  mapped_names? : Array[String] = [],
  mapped_env? : Environment? = None,
) -> Value {
  let new_args_props : Map[String, Value] = Map([])
  let new_args_descs : Map[String, PropDescriptor] = Map([])
  // §10.4.4.1: sloppy simple-param lists get live-mapped accessor slots so
  // mutations to arguments[i] reflect in the named param binding and vice versa.
  let map_count = if strict {
    0
  } else {
    match mapped_env {
      Some(_) => {
        let a = mapped_names.length()
        let b = args.length()
        if a < b {
          a
        } else {
          b
        }
      }
      None => 0
    }
  }
  // §10.4.4.7: walk backwards so only the last occurrence of each param name
  // gets an accessor; earlier occurrences of duplicates stay as data properties.
  let mapped_indices : Map[Int, Bool] = Map([])
  match mapped_env {
    Some(_) => {
      let mapped_set = @set.Set::default()
      let mut idx = map_count - 1
      while idx >= 0 {
        let pname = mapped_names[idx]
        if !mapped_set.contains(pname) {
          mapped_set.add(pname)
          mapped_indices[idx] = true
        }
        idx = idx - 1
      }
    }
    None => ()
  }
  for i = 0; i < map_count; i = i + 1 {
    let key = i.to_string()
    let pname = mapped_names[i]
    match (mapped_env, mapped_indices.get(i)) {
      (Some(env), Some(true)) => {
        let getter_fn = stamp_function_realm(
          Object({
            bag: PropertyBag(),
            prototype: Null,
            callable: Some(
              NativeCallable("[[MappedArgGetter]]", _a => env.get(pname)),
            ),
            class_name: "Function",
            extensible: true,
            arraybuffer_state: None,
          }),
          realm_state=Some(realm_state),
        )
        let setter_fn = stamp_function_realm(
          Object({
            bag: PropertyBag(),
            prototype: Null,
            callable: Some(
              NativeCallable("[[MappedArgSetter]]", a => {
                env.assign(pname, if a.length() > 0 { a[0] } else { Undefined })
                Undefined
              }),
            ),
            class_name: "Function",
            extensible: true,
            arraybuffer_state: None,
          }),
          realm_state=Some(realm_state),
        )
        new_args_props[key] = Undefined // accessor sentinel
        new_args_descs[key] = {
          writable: false,
          enumerable: true,
          configurable: true,
          getter: Some(getter_fn),
          setter: Some(setter_fn),
          is_accessor: true,
        }
      }
      // Duplicate earlier occurrence: plain data property, not live-mapped
      _ => {
        new_args_props[key] = args[i]
        new_args_descs[key] = {
          writable: true,
          enumerable: true,
          configurable: true,
          getter: None,
          setter: None,
          is_accessor: false,
        }
      }
    }
  }
  for i = map_count; i < args.length(); i = i + 1 {
    let key = i.to_string()
    new_args_props[key] = args[i]
    new_args_descs[key] = {
      writable: true,
      enumerable: true,
      configurable: true,
      getter: None,
      setter: None,
      is_accessor: false,
    }
  }
  new_args_props["length"] = Number(args.length().to_double())
  new_args_descs["length"] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  // §10.4.4.7 CreateMappedArgumentsObject: sloppy + simple params → writable callee
  // §10.4.4.6 CreateUnmappedArgumentsObject: strict OR non-simple params → ThrowTypeError
  // Discriminant: mapped_env is Some(_) iff we have a live-mapped (simple-params) env.
  if !strict && mapped_env is Some(_) {
    new_args_props["callee"] = ctor
    new_args_descs["callee"] = {
      writable: true,
      enumerable: false,
      configurable: true,
      getter: None,
      setter: None,
      is_accessor: false,
    }
  } else {
    // Strict mode OR non-simple params: install %ThrowTypeError% accessor for callee (§10.4.4.6 step 9)
    // Note: caller is NOT an own property of arguments objects — only callee is poisoned here.
    match throw_type_error {
      Some(tte) => {
        new_args_props["callee"] = Undefined // accessor sentinel
        new_args_descs["callee"] = {
          writable: false,
          enumerable: false,
          configurable: false,
          getter: Some(tte),
          setter: Some(tte),
          is_accessor: true,
        }
      }
      None => ()
    }
  }
  // Add Symbol.iterator using the realm's %ArrayProto_values% intrinsic;
  // each next() observes the live arguments object length and indexed props.
  let iter_sym = well_known_symbols.iterator
  let new_args_sym_props : Map[Int, Value] = Map([])
  let new_args_sym_descs : Map[Int, PropDescriptor] = Map([])
  new_args_sym_props[iter_sym.id] = arguments_iterator_value(realm_state)
  new_args_sym_descs[iter_sym.id] = {
    writable: true,
    enumerable: false,
    configurable: true,
    getter: None,
    setter: None,
    is_accessor: false,
  }
  let arguments_object = Object({
    bag: {
      properties: new_args_props,
      symbol_properties: new_args_sym_props,
      descriptors: new_args_descs,
      symbol_descriptors: new_args_sym_descs,
      internal_slots: Map([]),
      host_slots: Map([]),
    },
    prototype: get_obj_proto(realm_state=Some(realm_state)),
    callable: None,
    class_name: "Arguments",
    extensible: true,
    arraybuffer_state: None,
  })
  match arguments_object {
    Object(data) => {
      set_host_slot(data, runtime_arguments_host_slot, Undefined)
      arguments_object
    }
    _ => arguments_object
  }
}

///|
/// Shared class-constructor parameter binding and body execution.
/// Installs `arguments`, binds params with defaults/patterns, handles rest,
/// applies §10.2.11 param-env/body-env split, hoists, and executes `body`.
/// Caller handles: param validation, this/super/ setup on param_env,
/// and applying the ctor-specific return rule to the returned Signal.
fn Interpreter::bind_class_ctor_params_and_exec_body_signal(
  self : Interpreter,
  params : Array[@ast.Param],
  rest_param : String?,
  body : Array[@ast.Stmt],
  ctor_val : Value,
  args : Array[Value],
  param_env : Environment,
  ctx : ExecContext,
) -> Signal raise Error {
  let tte : Value? = if param_env.has("[[ThrowTypeError]]") {
    Some(param_env.get("[[ThrowTypeError]]"))
  } else {
    None
  }
  param_env.def(
    "arguments",
    make_arguments_object(
      self.realm_state,
      self.realm_state.well_known_symbols,
      args,
      ctor_val,
      true,
      throw_type_error=tte,
    ),
    VarBinding,
  )
  // §10.2.11 step 21: pre-declare all param BoundNames as TDZ so that
  // self- and forward-referring defaults throw ReferenceError.
  let mut has_rest_pattern_param = false
  for p in params {
    if p.is_rest_pattern {
      has_rest_pattern_param = true
      // Declare bound names from the rest destructuring pattern (...[a] → a),
      // but NOT the synthetic "$rest" name stored in rest_param.
      match p.pattern {
        Some(pat) =>
          for name in @static_semantics.bound_names(pat) {
            param_env.def_param_tdz(name)
          }
        None => ()
      }
      continue
    }
    match p.pattern {
      Some(pat) =>
        // Destructuring param: pre-declare all bound names (e.g. {y} → y).
        for name in @static_semantics.bound_names(pat) {
          param_env.def_param_tdz(name)
        }
      None => param_env.def_param_tdz(p.name)
    }
  }
  // Simple named rest (e.g. ...rest): pre-declare the user name.
  // Destructuring rest (...[a]): bound names declared above via BoundNames.
  if !has_rest_pattern_param {
    match rest_param {
      Some(rp) => param_env.def_param_tdz(rp)
      None => ()
    }
  }
  let mut effective_param_count = 0
  for i = 0; i < params.length(); i = i + 1 {
    let param = params[i]
    if param.is_rest_pattern {
      continue
    }
    let val : Value = if effective_param_count < args.length() &&
      !(args[effective_param_count] is Undefined) {
      args[effective_param_count]
    } else {
      match param.default_val {
        Some(default_expr) => self.eval_expr(ctx, default_expr, param_env)
        None =>
          if effective_param_count < args.length() {
            args[effective_param_count]
          } else {
            Undefined
          }
      }
    }
    match param.pattern {
      Some(pat) => self.bind_pattern(pat, val, param_env, LetBinding, ctx~)
      None => param_env.initialize(param.name, val)
    }
    effective_param_count += 1
  }
  match rest_param {
    Some(rest_name) => {
      let rest_elements : Array[Value] = []
      for i = effective_param_count; i < args.length(); i = i + 1 {
        rest_elements.push(args[i])
      }
      let rest_val = make_array(rest_elements)
      let mut bound_rest_pattern = false
      for p in params {
        if p.is_rest_pattern {
          match p.pattern {
            Some(pat) =>
              self.bind_pattern(pat, rest_val, param_env, LetBinding, ctx~)
            None => ()
          }
          bound_rest_pattern = true
          break
        }
      }
      if !bound_rest_pattern {
        param_env.initialize(rest_name, rest_val)
      }
    }
    None => ()
  }
  let split_scope = has_parameter_expressions(params)
  let body_env = if split_scope {
    let be = Environment::new(parent=Some(param_env))
    be.is_var_scope = true
    be
  } else {
    param_env
  }
  let param_source : Environment? = if split_scope {
    Some(param_env)
  } else {
    None
  }
  self.hoist_declarations(body, body_env, strict=true, param_source~)
  hoist_block_tdz(body, body_env)
  self.exec_stmts(ctx, body, body_env)
}

///|
pub fn Interpreter::construct_value(
  self : Interpreter,
  ctor : Value,
  args : Array[Value],
  loc : @token.Loc,
  proto_override? : Value? = None,
  new_target? : Value? = None,
) -> Value raise Error {
  self.with_active_value(fn() raise {
    with_active_callee_realm_value(self.realm_state, ctor, fn() raise {
      self.construct_value_impl(ctor, args, loc, proto_override~, new_target~)
    })
  })
}

///|
fn Interpreter::construct_value_impl(
  self : Interpreter,
  ctor : Value,
  args : Array[Value],
  loc : @token.Loc,
  proto_override? : Value? = None,
  new_target? : Value? = None,
) -> Value raise Error {
  // Effective newTarget: explicit override (e.g. from Reflect.construct) or the
  // constructor itself (the normal `new Foo()` case, per §10.2.2 step 4).
  let new_target = new_target.unwrap_or(ctor)
  match ctor {
    Proxy(proxy_data) => {
      // §10.5.13 [[Construct]]: target must be a constructor per IsConstructor,
      // not merely callable — this rejects Proxy wrapping method-shorthand
      // (is_method: true), arrow functions, and other non-constructable callables.
      let target = get_proxy_target(proxy_data)
      if !is_constructor_value(target) {
        raise @errors.TypeError(message="target is not a constructor")
      }
      let trap = get_proxy_trap(proxy_data, "construct", self)
      match trap {
        Some(trap_fn) => {
          let handler = get_proxy_handler(proxy_data)
          let args_array : Value = make_array(args.copy())
          let result = self.call_value(
            trap_fn,
            handler,
            [target, args_array, new_target],
            loc,
          )
          match result {
            Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) | Proxy(_) =>
              result
            _ =>
              raise @errors.TypeError(
                message="'construct' on proxy: trap returned non-Object",
              )
          }
        }
        None =>
          self.construct_value(
            target,
            args,
            loc,
            proto_override~,
            new_target=Some(new_target),
          )
      }
    }
    Object(obj_data) =>
      match obj_data.callable {
        Some(UserFunc(data)) => {
          // §13.3.5.1 step 5: method-shorthand functions have no [[Construct]]
          if data.is_method {
            raise @errors.TypeError(
              message=format_loc_context("is not a constructor", loc),
            )
          }
          let func_strict = data.strict
          let func_ctx : ExecContext = {
            strict: func_strict,
            current_generator: None,
          }
          if func_strict {
            // Strict mode: check for duplicate parameters and reserved names
            check_duplicate_params(data.params)
            for p in data.params {
              @static_semantics.validate_strict_binding_name(p)
            }
          }
          let proto = match proto_override {
            Some(p) => p
            None => self.get_prototype_from_constructor(new_target, loc)
          }
          let new_obj = make_constructor_instance(proto, "Object")
          let func_env = Environment::new(parent=Some(data.closure))
          func_env.is_var_scope = true
          func_env.def_builtin("[[EvalMethodContext]]", Bool(data.is_method))
          func_env.def("this", new_obj, LetBinding)
          func_env.def("", new_target, LetBinding)
          for i = 0; i < data.params.length(); i = i + 1 {
            let val : Value = if i < args.length() {
              args[i]
            } else {
              Undefined
            }
            // In sloppy mode, duplicate params are allowed; last value wins
            if func_env.bindings.contains(data.params[i]) {
              func_env.assign(data.params[i], val)
            } else {
              func_env.def_parameter(data.params[i], val)
            }
          }
          if !params_include_arguments(data.params, None) {
            let tte : Value? = if func_env.has("[[ThrowTypeError]]") {
              Some(func_env.get("[[ThrowTypeError]]"))
            } else {
              None
            }
            func_env.def(
              "arguments",
              make_arguments_object(
                self.realm_state,
                self.realm_state.well_known_symbols,
                args,
                ctor,
                func_strict,
                throw_type_error=tte,
                mapped_names=data.params,
                mapped_env=Some(func_env),
              ),
              VarBinding,
            )
          }
          // Hoist declarations and top-level lexical TDZ markers.
          self.hoist_declarations(data.body, func_env, strict=func_strict)
          hoist_block_tdz(data.body, func_env)
          if data.has_name_binding {
            match data.name {
              Some(name) =>
                if !func_env.bindings.contains(name) {
                  func_env.def(name, ctor, FunctionNameBinding)
                }
              None => ()
            }
          }
          let exec_result = self.exec_stmts(func_ctx, data.body, func_env)
          raise_if_break_continue(exec_result)
          match exec_result {
            Normal(_) => new_obj
            ReturnSignal(v) =>
              if is_object_like_for_constructor_return(v) {
                v
              } else {
                new_obj
              }
            _ => new_obj
          }
        }
        Some(UserFuncExt(data)) => {
          // §13.3.5.1 step 5: method-shorthand functions have no [[Construct]]
          if data.is_method {
            raise @errors.TypeError(
              message=format_loc_context("is not a constructor", loc),
            )
          }
          let func_strict = data.strict
          let func_ctx : ExecContext = {
            strict: func_strict,
            current_generator: None,
          }
          if func_strict {
            // Strict mode: check for duplicate parameters and reserved names
            check_duplicate_params_ext(data.params, data.rest_param)
            validate_strict_param_binding_names_ext(
              data.params,
              data.rest_param,
            )
          }
          let proto = match proto_override {
            Some(p) => p
            None => self.get_prototype_from_constructor(new_target, loc)
          }
          let new_obj = make_constructor_instance(proto, "Object")
          // §15.2.5: install the self-name on a dedicated env between
          // data.closure and param_env so default expressions can see it
          // (`function f(a = () => f) {}`). Params on param_env shadow
          // naturally; body-local decls of the same name shadow via
          // lexical lookup. Gated on has_name_binding — methods and
          // function declarations don't get this binding.
          let self_name_env : Environment = if data.has_name_binding {
            match data.name {
              Some(name) => {
                let ne = Environment::new(parent=Some(data.closure))
                ne.def(name, ctor, FunctionNameBinding)
                ne
              }
              None => data.closure
            }
          } else {
            data.closure
          }
          let param_env = Environment::new(parent=Some(self_name_env))
          param_env.is_var_scope = true
          param_env.def_builtin("[[EvalMethodContext]]", Bool(data.is_method))
          param_env.def("this", new_obj, LetBinding)
          param_env.def("", new_target, LetBinding)
          if !ext_params_include_arguments(data.params, data.rest_param) {
            // Create arguments object before binding params so defaults can reference it,
            // unless a formal parameter is named `arguments`.
            let tte2 : Value? = if param_env.has("[[ThrowTypeError]]") {
              Some(param_env.get("[[ThrowTypeError]]"))
            } else {
              None
            }
            param_env.def(
              "arguments",
              make_arguments_object(
                self.realm_state,
                self.realm_state.well_known_symbols,
                args,
                ctor,
                func_strict,
                throw_type_error=tte2,
              ),
              VarBinding,
            )
          }
          // ES §10.2.2 [[Construct]] steps 13-14: object-like return
          // replaces the instance; anything else yields `new_obj`.
          match
            self.bind_ext_params_and_exec_body_signal(
              data,
              args,
              param_env,
              func_ctx,
              is_arrow=false,
            ) {
            ReturnSignal(v) =>
              if is_object_like_for_constructor_return(v) {
                v
              } else {
                new_obj
              }
            _ => new_obj
          }
        }
        Some(BoundFunc(target, _, bound_args)) => {
          // new BoundFunc(...args) delegates to new Target(...boundArgs, ...args)
          // §10.4.1.2 [[Construct]] step 6-7: if newTarget === bound function,
          // replace with target; otherwise forward the explicit newTarget.
          // When newTarget is rewritten to target (nt_for_target=None), drop
          // proto_override too — it was derived from the bound function's
          // .prototype (absent on bound fns → Null), and the effective newTarget
          // is now target, so the instance prototype must come from target.
          let nt_for_target = if physical_equal(new_target, ctor) {
            None // let recursive call default to target
          } else {
            Some(new_target)
          }
          let effective_proto_override : Value? = if nt_for_target is None {
            None
          } else {
            proto_override
          }
          let all_args : Array[Value] = []
          for a in bound_args {
            all_args.push(a)
          }
          for a in args {
            all_args.push(a)
          }
          self.construct_value(
            target,
            all_args,
            loc,
            proto_override=effective_proto_override,
            new_target=nt_for_target,
          )
        }
        Some(NativeCallable(_, func)) => func(args)
        Some(NativeCallableWithContext(_, func)) =>
          func(ConstructWithTarget(new_target), args)
        Some(NonConstructableCallable(name, _)) =>
          raise @errors.TypeError(
            message=format_loc_context(name + " is not a constructor", loc),
          )
        Some(NonConstructableInterpreterCallable(name, _)) =>
          raise @errors.TypeError(
            message=format_loc_context(name + " is not a constructor", loc),
          )
        Some(InterpreterCallable(_name, func)) => {
          if !interpreter_callable_is_constructor(obj_data) {
            raise @errors.TypeError(
              message=format_loc_context(_name + " is not a constructor", loc),
            )
          }
          func(self, Undefined, args)
        }
        Some(InterpreterCallableWithContext(_name, func)) => {
          if !interpreter_callable_is_constructor(obj_data) {
            raise @errors.TypeError(
              message=format_loc_context(_name + " is not a constructor", loc),
            )
          }
          if _name == "Symbol" {
            raise @errors.TypeError(message="Symbol is not a constructor")
          }
          func(self, ConstructWithTarget(new_target), Undefined, args)
        }
        Some(ExecutorCallable(executable)) => {
          if !executable.is_constructable() {
            raise @errors.TypeError(
              message=format_loc_context(
                executable.name() + " is not a constructor",
                loc,
              ),
            )
          }
          self.run_executor_function(
            executable,
            ctor,
            ConstructWithTarget(new_target),
            Undefined,
            args,
          )
        }
        Some(MethodCallable(name, _)) =>
          raise @errors.TypeError(
            message=format_loc_context(name + " is not a constructor", loc),
          )
        Some(ConstructorOnlyCallable(_, func)) => func(self, args)
        Some(ArrowFunc(_)) | Some(ArrowFuncExt(_)) =>
          raise @errors.TypeError(
            message=format_loc_context(
              "arrow functions cannot be used as constructors", loc,
            ),
          )
        Some(
          ClassConstructor(
            {
              name: class_name,
              proto,
              super_ctor,
              ctor_fn,
              closure,
              super_proto,
              instance_fields,
              private_instance_fields,
              private_brand,
              ..,
            }
          )
        ) => {
          // Class bodies are always strict mode
          let class_ctx : ExecContext = {
            strict: true,
            current_generator: None,
          }
          // Create the new instance with the class prototype
          let result = {
            // Create the new instance with the class prototype
            // Base constructors allocate their receiver here, so this is the
            // point where OrdinaryCreateFromConstructor observes newTarget.
            // Derived constructors allocate only when super() reaches a base
            // constructor; their local object is an unobservable fallback used
            // by the evaluator and must not read newTarget.prototype early.
            let instance_proto = match super_ctor {
              Some(_) => proto
              None =>
                match proto_override {
                  Some(value) => value
                  None =>
                    if physical_equal(new_target, ctor) {
                      proto
                    } else {
                      self.get_prototype_from_constructor(new_target, loc)
                    }
                }
            }
            let new_obj = make_constructor_instance(instance_proto, class_name)
            // Set up the constructor environment
            let ctor_env = Environment::new(parent=Some(closure))
            ctor_env.is_var_scope = true
            // For derived classes, 'this' is in TDZ until super() is called
            match super_ctor {
              Some(_) => {
                // Put 'this' in TDZ - will be initialized by super() call
                ctor_env.def_tdz("this", LetBinding)
                // Keep the active class function, not a snapshot of its
                // [[Prototype]]. Each SuperCall performs GetSuperConstructor
                // immediately before evaluating that call's arguments.
                ctor_env.def_builtin("[[ActiveClassFunction]]", ctor)
                ctor_env.def_builtin("[[SuperPrototype]]", super_proto)
                // Store the new instance for super() to use
                ctor_env.def_builtin("[[PendingThis]]", new_obj)
                // For explicit constructors: fields are deferred; super() will
                // call [[InitInstanceFields]] after establishing `this`.
                // For implicit constructors (ctor_fn=None): fields are applied
                // directly at each return/fallthrough site in the None arm below.
                if ctor_fn is Some(_) &&
                  (
                    instance_fields.length() > 0 ||
                    private_instance_fields.length() > 0
                  ) {
                  ctor_env.def_builtin(
                    "[[InitInstanceFields]]",
                    make_instance_fields_init(
                      instance_fields,
                      private_instance_fields,
                      Some(private_brand),
                      self.realm_state,
                    ),
                  )
                }
              }
              None => {
                ctor_env.def_builtin("this", new_obj)
                // Keep [[SuperPrototype]] directly available to constructor
                // parameter defaults that evaluate super.prop.
                ctor_env.def_builtin("[[SuperPrototype]]", super_proto)
                // Base class: initialize instance fields before constructor body
                self.install_instance_fields(new_obj, instance_fields)
                self.install_private_fields(
                  new_obj, private_instance_fields, private_brand,
                )
              }
            }
            ctor_env.def("", new_target, LetBinding)
            // Execute the constructor if one was defined
            match ctor_fn {
              Some((params, rest_param, body)) => {
                // Class constructors are always strict — validate params
                check_duplicate_params_ext(params, rest_param)
                validate_strict_param_binding_names_ext(params, rest_param)
                let exec_result = self.bind_class_ctor_params_and_exec_body_signal(
                  params, rest_param, body, ctor, args, ctor_env, class_ctx,
                )
                raise_if_break_continue(exec_result)
                match exec_result {
                  Normal(_) =>
                    apply_class_constructor_return(
                      ctor_env,
                      super_ctor,
                      Undefined,
                    )
                  ReturnSignal(v) =>
                    apply_class_constructor_return(ctor_env, super_ctor, v)
                  _ =>
                    apply_class_constructor_return(
                      ctor_env,
                      super_ctor,
                      Undefined,
                    )
                }
              }
              None => {
                // The default derived constructor is specified as
                // `constructor(...args) { super(...args); }`. Re-enter the
                // canonical [[Construct]] path so Proxy traps, forwarded
                // newTarget, nested derived classes, and abrupt completions all
                // share exactly the same behavior as an explicit super() call.
                match super_ctor {
                  Some(_) => {
                    let sc = obj_data.prototype
                    if !is_constructor_value(sc) {
                      raise @errors.TypeError(
                        message="super constructor is not a constructor",
                      )
                    }
                    let super_result = self.construct_value(
                      sc,
                      args,
                      @token.Loc::default(),
                      new_target=Some(new_target),
                    )
                    self.install_instance_fields(super_result, instance_fields)
                    self.install_private_fields(
                      super_result, private_instance_fields, private_brand,
                    )
                    return super_result
                  }
                  None => ()
                }
                new_obj
              }
            }
          }
          result
        }
        _ =>
          raise @errors.TypeError(
            message=format_loc_context("is not a constructor", loc),
          )
      }
    _ =>
      raise @errors.TypeError(
        message=format_loc_context("is not a constructor", loc),
      )
  }
}