///|
/// Reflection/object-model builtins (iter/next/property/bool/repr/callable/type/etc).

///|
let abc_cache_token_ref : Ref[@bigint.BigInt] = { val: 0N, }

///|
// `id()` needs stable object identity, especially for cycle detection in the
// stdlib (e.g. pprint's recursion tracking). We don't have direct pointer->int
// casts available, so we maintain small registries keyed by reference identity.
let id_registry_string : Ref[Array[(String, @bigint.BigInt)]] = { val: [], }

///|
let id_registry_int_array : Ref[Array[(Array[Int32], @bigint.BigInt)]] = {
  val: [],
}

///|
let id_registry_value_array : Ref[Array[(Array[Value], @bigint.BigInt)]] = {
  val: [],
}

///|
let id_registry_dict_pairs : Ref[Array[(Array[(Value, Value)], @bigint.BigInt)]] = {
  val: [],
}

///|
fn id_for_string(value : String) -> @bigint.BigInt {
  for pair in id_registry_string.val {
    if physical_equal(pair.0, value) {
      return pair.1
    }
  }
  let id = fresh_object_hashvalue()
  id_registry_string.val.push((value, id))
  id
}

///|
fn id_for_int_array(value : Array[Int32]) -> @bigint.BigInt {
  for pair in id_registry_int_array.val {
    if physical_equal(pair.0, value) {
      return pair.1
    }
  }
  let id = fresh_object_hashvalue()
  id_registry_int_array.val.push((value, id))
  id
}

///|
fn id_for_value_array(value : Array[Value]) -> @bigint.BigInt {
  for pair in id_registry_value_array.val {
    if physical_equal(pair.0, value) {
      return pair.1
    }
  }
  let id = fresh_object_hashvalue()
  id_registry_value_array.val.push((value, id))
  id
}

///|
fn id_for_dict_pairs(value : Array[(Value, Value)]) -> @bigint.BigInt {
  for pair in id_registry_dict_pairs.val {
    if physical_equal(pair.0, value) {
      return pair.1
    }
  }
  let id = fresh_object_hashvalue()
  id_registry_dict_pairs.val.push((value, id))
  id
}

///|
fn builtin_iter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("iter", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "iter() takes exactly one argument".to_string(),
      ),
    )
  }
  let _ = locals
  iter_value_to_iterator(positional[0], globals, builtins, io)
}

///|
fn builtin_next(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("next", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "next() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  let default_value : Value? = if positional.length() == 2 {
    Some(positional[1])
  } else {
    None
  }
  let _ = locals
  iterator_next(positional[0], default_value, globals, builtins, io)
}

///|
fn builtin_sys_exc_info(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sys.exc_info", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "exc_info() takes no arguments".to_string(),
      ),
    )
  }
  match active_exception() {
    None => Ok(Value::Tuple([Value::None, Value::None, Value::None]))
    Some(err) => {
      let exc_value = exception_value_from_runtime_error(
        err, globals, builtins, io,
      )
      let exc_type_value = match exc_value {
        Value::Instance(inst) => Value::Class(inst.class)
        _ => Value::Str(err.exc_type)
      }
      let frame_builtins = filter_frame_builtins(builtins)
      let frame_builtins_pairs : Array[(Value, Value)] = []
      for pair in frame_builtins {
        frame_builtins_pairs.push((Value::Str(pair.0), pair.1))
      }
      let tb_value = make_traceback_value(
        err.traceback,
        Value::Dict(frame_builtins_pairs),
        err.span,
      )
      Ok(Value::Tuple([exc_type_value, exc_value, tb_value]))
    }
  }
}

///|
fn builtin_sys_exception(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sys.exception", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "exception() takes no arguments".to_string(),
      ),
    )
  }
  match active_exception() {
    None => Ok(Value::None)
    Some(err) =>
      Ok(exception_value_from_runtime_error(err, globals, builtins, io))
  }
}

///|
fn builtin_sys_getframe(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sys._getframe", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() > 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "_getframe() takes 0 or 1 arguments".to_string(),
      ),
    )
  }
  let mut depth = 0
  if positional.length() == 1 {
    depth = match index_from_value(positional[0], 0) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
  }
  if depth < 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: call stack is not deep enough".to_string(),
      ),
    )
  }
  // `snapshot_traceback()` is exception-focused (innermost-only). For
  // `sys._getframe`, we need the full active call stack.
  let frames = active_frame_stack.val
  match current_span() {
    Some(span) =>
      if frames.length() > 0 {
        let mut last = frames[frames.length() - 1]
        let mut width = span.end - span.start
        if width <= 0 {
          width = 1
        }
        let end_column = if width > 512 { 0 } else { span.column + width }
        last = TracebackFrame::{
          name: last.name,
          filename: last.filename,
          line: span.line,
          column: span.column,
          end_column,
        }
        frames[frames.length() - 1] = last
      }
    None => ()
  }
  let envs = active_frame_env_stack.val
  let frame_builtins = filter_frame_builtins(builtins)
  let frame_builtins_pairs : Array[(Value, Value)] = []
  for pair in frame_builtins {
    frame_builtins_pairs.push((Value::Str(pair.0), pair.1))
  }
  let frame_builtins_dict = Value::Dict(frame_builtins_pairs)
  if depth >= frames.length() {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: call stack is not deep enough".to_string(),
      ),
    )
  }
  let idx = frames.length() - 1 - depth
  // Build a best-effort f_back chain up to the requested depth. We don't
  // currently model real frame globals/locals, but stdlib helpers (traceback,
  // inspect, unittest) expect the chain structure to exist.
  let mut chain : Value = Value::None
  for j = 0; j <= idx; j = j + 1 {
    let frame_value = make_frame_value(frames[j], frame_builtins_dict)
    match frame_value {
      Value::Instance(inst) => {
        // `types.TracebackType(..., frame, lasti=0, ...)` expects a "no range"
        // position payload at instruction 0 (GH-93249). Provide a stable
        // default here for frames created via sys._getframe().
        match get_named_value(inst.dict, "f_code") {
          Some(Value::Instance(code_inst)) =>
            set_named_value(
              code_inst.dict,
              "__mpython_co_positions",
              Value::List([
                Value::Tuple([
                  Value::None,
                  Value::None,
                  Value::Int(0N),
                  Value::Int(0N),
                ]),
              ]),
            )
          _ => ()
        }
        // Best-effort locals/globals for introspection.
        let env_opt = if j < envs.length() { Some(envs[j]) } else { None }
        match env_opt {
          Some(env) => {
            let locals_pairs : Array[(Value, Value)] = []
            for pair in env.locals {
              if !pair.0.has_prefix("$__") {
                // Locals are stored as cells; expose a CPython-like `f_locals`
                // dict with the current concrete values and omit unbound cells.
                let value_opt = if is_cell_value(pair.1) {
                  cell_get_value(pair.1)
                } else {
                  Some(pair.1)
                }
                match value_opt {
                  Some(v) => locals_pairs.push((Value::Str(pair.0), v))
                  None => ()
                }
              }
            }
            set_named_value(inst.dict, "f_locals", Value::Dict(locals_pairs))
            let filename = frames[j].filename
            let is_unittest = filename.contains("/Lib/unittest/") ||
              filename.contains("\\Lib\\unittest\\")
            if !is_unittest {
              let globals_pairs : Array[(Value, Value)] = []
              for pair in env.globals {
                if !pair.0.has_prefix("$__") {
                  globals_pairs.push((Value::Str(pair.0), pair.1))
                }
              }
              set_named_value(
                inst.dict,
                "f_globals",
                Value::Dict(globals_pairs),
              )
            }
          }
          None => ()
        }
        match chain {
          Value::None => ()
          _ => set_named_value(inst.dict, "f_back", chain)
        }
        chain = Value::Instance(inst)
      }
      _ => ()
    }
  }
  Ok(chain)
}

///|
fn bigint_to_base_string(
  value_in : @bigint.BigInt,
  base : Int32,
  prefix : String,
) -> String {
  // NOTE: BigInt already provides `to_string()` for decimal.
  // For non-decimal, we implement a simple base conversion using div/mod.
  if value_in == 0N {
    return prefix + "0"
  }
  let digits = if base == 2 {
    "01"
  } else if base == 8 {
    "01234567"
  } else {
    "0123456789abcdef"
  }
  let mut value = value_in
  let mut sign = ""
  if value < 0N {
    sign = "-"
    value = -value
  }
  let b = @bigint.BigInt::from_int(base)
  let chars : Array[Char] = []
  let mut n = value
  while n > 0N {
    let d = (n % b).to_int()
    chars.push(digits.to_array()[d])
    n = n / b
  }
  let buf = StringBuilder()
  buf.write_string(sign)
  buf.write_string(prefix)
  for i = chars.length() - 1; i >= 0; i = i - 1 {
    buf.write_char(chars[i])
    if i == 0 {
      break
    }
  }
  buf.to_string()
}

///|
fn escape_string_for_repr_with_quote(text : String, quote : Char) -> String {
  let buf = StringBuilder()
  for ch in text.to_array() {
    if ch == '\\' {
      buf.write_string("\\\\")
    } else if ch == quote {
      buf.write_char('\\')
      buf.write_char(ch)
    } else if ch == '\n' {
      buf.write_string("\\n")
    } else if ch == '\r' {
      buf.write_string("\\r")
    } else if ch == '\t' {
      buf.write_string("\\t")
    } else {
      buf.write_char(ch)
    }
  }
  buf.to_string()
}

///|
fn repr_string(text : String) -> String {
  let use_double = text.contains("'") && !text.contains("\"")
  let quote = if use_double { '"' } else { '\'' }
  let escaped = escape_string_for_repr_with_quote(text, quote)
  quote.to_string() + escaped + quote.to_string()
}

///|
fn repr_fallback(value : Value) -> String {
  fn write_joined(
    buf : StringBuilder,
    values : Array[Value],
    prefix : String,
    suffix : String,
    comma_for_single : Bool,
  ) -> Unit {
    buf.write_string(prefix)
    for i = 0; i < values.length(); i = i + 1 {
      if i > 0 {
        buf.write_string(", ")
      }
      buf.write_string(repr_fallback(values[i]))
    }
    if comma_for_single && values.length() == 1 {
      buf.write_char(',')
    }
    buf.write_string(suffix)
  }

  match value {
    Value::Str(text) => repr_string(text)
    Value::Bytes(values) => bytes_repr(values)
    Value::ByteArray(values) => "bytearray(" + bytes_repr(values) + ")"
    Value::MemoryView(values) => "memoryview(" + bytes_repr(values) + ")"
    Value::List(values) => {
      let buf = StringBuilder()
      write_joined(buf, values, "[", "]", false)
      buf.to_string()
    }
    Value::Tuple(values) => {
      let buf = StringBuilder()
      write_joined(buf, values, "(", ")", true)
      buf.to_string()
    }
    Value::Dict(pairs) => {
      let buf = StringBuilder()
      buf.write_char('{')
      for i = 0; i < pairs.length(); i = i + 1 {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(repr_fallback(pairs[i].0))
        buf.write_string(": ")
        buf.write_string(repr_fallback(pairs[i].1))
      }
      buf.write_char('}')
      buf.to_string()
    }
    Value::Set(values) =>
      if values.length() == 0 {
        "set()"
      } else {
        let buf = StringBuilder()
        write_joined(buf, values, "{", "}", false)
        buf.to_string()
      }
    Value::Instance(inst) =>
      match get_named_value(inst.dict, list_storage_name) {
        Some(Value::List(values)) => repr_fallback(Value::List(values))
        _ =>
          match get_named_value(inst.dict, tuple_storage_name) {
            Some(Value::Tuple(values)) => repr_fallback(Value::Tuple(values))
            _ =>
              match get_named_value(inst.dict, dict_storage_name) {
                Some(Value::Dict(pairs)) => repr_fallback(Value::Dict(pairs))
                _ =>
                  match get_named_value(inst.dict, set_storage_name) {
                    Some(Value::Set(values)) =>
                      if values.length() == 0 {
                        inst.class.name + "()"
                      } else {
                        let buf = StringBuilder()
                        buf.write_string(inst.class.name)
                        buf.write_char('(')
                        write_joined(buf, values, "{", "}", false)
                        buf.write_char(')')
                        buf.to_string()
                      }
                    _ => {
                      let module_name = match
                        get_named_value(inst.class.dict, "__module__") {
                        Some(Value::Str(text)) => text
                        _ => inst.class.name
                      }
                      let qualname = match
                        get_named_value(inst.class.dict, "__qualname__") {
                        Some(Value::Str(text)) => text
                        _ => inst.class.name
                      }
                      let id_text = match
                        get_named_value(inst.dict, "hashvalue") {
                        Some(Value::Int(v)) => v.to_string()
                        Some(Value::Bool(v)) =>
                          (if v { 1N } else { 0N }).to_string()
                        _ => "0"
                      }
                      "<" +
                      module_name +
                      "." +
                      qualname +
                      " object at 0x" +
                      id_text +
                      ">"
                    }
                  }
              }
          }
      }
    _ => value_to_string(value)
  }
}

///|
fn levenshtein_distance_fast(
  a_in : String,
  b_in : String,
  max_cost : Int32,
) -> Int32 {
  let move_cost = 2
  let case_cost = 1
  let max_size = 40
  if a_in == b_in {
    return 0
  }

  // Trim common affixes (prefix + suffix), mirroring Lib/traceback.py.
  let a_chars = a_in.to_array()
  let b_chars = b_in.to_array()
  let mut a_start = 0
  let mut b_start = 0
  while a_start < a_chars.length() &&
        b_start < b_chars.length() &&
        a_chars[a_start] == b_chars[b_start] {
    a_start = a_start + 1
    b_start = b_start + 1
  }
  let mut a_end = a_chars.length()
  let mut b_end = b_chars.length()
  while a_end > a_start &&
        b_end > b_start &&
        a_chars[a_end - 1] == b_chars[b_end - 1] {
    a_end = a_end - 1
    b_end = b_end - 1
  }
  let a_len = a_end - a_start
  let b_len = b_end - b_start
  if a_len == 0 || b_len == 0 {
    return move_cost * (a_len + b_len)
  }
  if a_len > max_size || b_len > max_size {
    return max_cost + 1
  }
  let a : Array[Char] = []
  for i = a_start; i < a_end; i = i + 1 {
    a.push(a_chars[i])
  }
  let b : Array[Char] = []
  for i = b_start; i < b_end; i = i + 1 {
    b.push(b_chars[i])
  }
  let mut a2 = a
  let mut b2 = b
  if b2.length() < a2.length() {
    let tmp = a2
    a2 = b2
    b2 = tmp
  }
  let la = a2.length()
  let lb = b2.length()
  if (lb - la) * move_cost > max_cost {
    return max_cost + 1
  }
  fn ascii_lower(ch : Char) -> Int32 {
    let n = ch.to_int()
    if n >= 'A'.to_int() && n <= 'Z'.to_int() {
      n + 32
    } else {
      n
    }
  }

  fn subst_cost(ch_a : Char, ch_b : Char) -> Int32 {
    if ch_a == ch_b {
      0
    } else if ascii_lower(ch_a) == ascii_lower(ch_b) {
      case_cost
    } else {
      move_cost
    }
  }

  let row : Array[Int32] = []
  for i = 1; i <= la; i = i + 1 {
    row.push(move_cost * i)
  }
  let mut result = 0
  for bindex = 0; bindex < lb; bindex = bindex + 1 {
    let bchar = b2[bindex]
    let mut distance = bindex * move_cost
    result = distance
    let mut minimum = 2147483647
    for index = 0; index < la; index = index + 1 {
      let substitute = distance + subst_cost(bchar, a2[index])
      distance = row[index]
      let insert_delete = (if result < distance { result } else { distance }) +
        move_cost
      let next = if insert_delete < substitute {
        insert_delete
      } else {
        substitute
      }
      row[index] = next
      result = next
      if result < minimum {
        minimum = result
      }
    }
    if minimum > max_cost {
      return max_cost + 1
    }
  }
  result
}

///|
fn builtin_mpython_levenshtein_distance(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("__mpython_levenshtein_distance", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__mpython_levenshtein_distance() takes exactly 3 arguments".to_string(),
      ),
    )
  }
  let a = match positional[0] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "__mpython_levenshtein_distance() arg 1 must be str".to_string(),
        ),
      )
  }
  let b = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "__mpython_levenshtein_distance() arg 2 must be str".to_string(),
        ),
      )
  }
  let max_cost = match index_from_value(positional[2], 0) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(
    Value::Int(
      @bigint.BigInt::from_int(levenshtein_distance_fast(a, b, max_cost)),
    ),
  )
}

///|
fn make_property_instance(
  klass : ClassValue,
  fget : Value,
  fset : Value,
  fdel : Value,
  doc : Value,
) -> Value {
  let dict : Array[(String, Value)] = []
  dict.push(("fget", fget))
  dict.push(("fset", fset))
  dict.push(("fdel", fdel))
  dict.push(("doc", doc))
  Value::Instance(InstanceValue::{ class: klass, dict, })
}

///|

///|
fn builtin_property(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = locals
  if positional.length() > 4 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "property() takes at most 4 arguments".to_string(),
      ),
    )
  }
  let mut fget = if positional.length() >= 1 {
    positional[0]
  } else {
    Value::None
  }
  let mut fset = if positional.length() >= 2 {
    positional[1]
  } else {
    Value::None
  }
  let mut fdel = if positional.length() >= 3 {
    positional[2]
  } else {
    Value::None
  }
  let mut doc = if positional.length() >= 4 {
    positional[3]
  } else {
    Value::None
  }
  let mut has_fget = positional.length() >= 1
  let mut has_fset = positional.length() >= 2
  let mut has_fdel = positional.length() >= 3
  let mut has_doc = positional.length() >= 4
  for pair in keywords {
    let key = pair.0
    let value = pair.1
    match key {
      "fget" =>
        if has_fget {
          return Err(multiple_values_error("property", "fget"))
        } else {
          fget = value
          has_fget = true
        }
      "fset" =>
        if has_fset {
          return Err(multiple_values_error("property", "fset"))
        } else {
          fset = value
          has_fset = true
        }
      "fdel" =>
        if has_fdel {
          return Err(multiple_values_error("property", "fdel"))
        } else {
          fdel = value
          has_fdel = true
        }
      "doc" =>
        if has_doc {
          return Err(multiple_values_error("property", "doc"))
        } else {
          doc = value
          has_doc = true
        }
      _ => return Err(unexpected_keyword_argument_error("property", key))
    }
  }
  if doc is Value::None && !(fget is Value::None) {
    match get_attr_from_value(fget, "__doc__", globals, builtins, io) {
      Ok(value) => doc = value
      Err(err) =>
        match err.kind {
          RuntimeErrorKind::Attribute => ()
          _ => return Err(err)
        }
    }
  }
  Ok(
    make_property_instance(
      builtin_class_from_name("property", builtins),
      fget,
      fset,
      fdel,
      doc,
    ),
  )
}

///|
fn builtin_property_init(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = locals
  let _ = globals
  let _ = builtins
  let _ = io
  if positional.length() < 1 || positional.length() > 5 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "property.__init__() takes from 1 to 5 positional arguments".to_string(),
      ),
    )
  }
  let target = positional[0]
  let mut fget = if positional.length() >= 2 {
    positional[1]
  } else {
    Value::None
  }
  let mut fset = if positional.length() >= 3 {
    positional[2]
  } else {
    Value::None
  }
  let mut fdel = if positional.length() >= 4 {
    positional[3]
  } else {
    Value::None
  }
  let mut doc = if positional.length() >= 5 {
    positional[4]
  } else {
    Value::None
  }
  let mut has_fget = positional.length() >= 2
  let mut has_fset = positional.length() >= 3
  let mut has_fdel = positional.length() >= 4
  let mut has_doc = positional.length() >= 5
  for pair in keywords {
    let key = pair.0
    let value = pair.1
    match key {
      "fget" =>
        if has_fget {
          return Err(multiple_values_error("property.__init__", "fget"))
        } else {
          fget = value
          has_fget = true
        }
      "fset" =>
        if has_fset {
          return Err(multiple_values_error("property.__init__", "fset"))
        } else {
          fset = value
          has_fset = true
        }
      "fdel" =>
        if has_fdel {
          return Err(multiple_values_error("property.__init__", "fdel"))
        } else {
          fdel = value
          has_fdel = true
        }
      "doc" =>
        if has_doc {
          return Err(multiple_values_error("property.__init__", "doc"))
        } else {
          doc = value
          has_doc = true
        }
      _ =>
        return Err(unexpected_keyword_argument_error("property.__init__", key))
    }
  }
  match target {
    Value::Instance(inst) => {
      set_named_value(inst.dict, "fget", fget)
      set_named_value(inst.dict, "fset", fset)
      set_named_value(inst.dict, "fdel", fdel)
      set_named_value(inst.dict, "doc", doc)
      Ok(Value::None)
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "property.__init__() expects a property instance".to_string(),
        ),
      )
  }
}

///|
fn builtin_property_getter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("property.getter", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "getter() takes exactly two arguments".to_string(),
      ),
    )
  }
  let prop = positional[0]
  let fget = positional[1]
  match prop {
    Value::Instance(inst) => {
      let fset = match get_named_value(inst.dict, "fset") {
        Some(v) => v
        None => Value::None
      }
      let fdel = match get_named_value(inst.dict, "fdel") {
        Some(v) => v
        None => Value::None
      }
      let doc = match get_named_value(inst.dict, "doc") {
        Some(v) => v
        None => Value::None
      }
      Ok(make_property_instance(inst.class, fget, fset, fdel, doc))
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "getter() expects a property".to_string(),
        ),
      )
  }
}

///|
fn builtin_property_setter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("property.setter", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "setter() takes exactly two arguments".to_string(),
      ),
    )
  }
  let prop = positional[0]
  let fset = positional[1]
  match prop {
    Value::Instance(inst) => {
      let fget = match get_named_value(inst.dict, "fget") {
        Some(v) => v
        None => Value::None
      }
      let fdel = match get_named_value(inst.dict, "fdel") {
        Some(v) => v
        None => Value::None
      }
      let doc = match get_named_value(inst.dict, "doc") {
        Some(v) => v
        None => Value::None
      }
      Ok(make_property_instance(inst.class, fget, fset, fdel, doc))
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "setter() expects a property".to_string(),
        ),
      )
  }
}

///|
fn builtin_property_deleter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("property.deleter", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "deleter() takes exactly two arguments".to_string(),
      ),
    )
  }
  let prop = positional[0]
  let fdel = positional[1]
  match prop {
    Value::Instance(inst) => {
      let fget = match get_named_value(inst.dict, "fget") {
        Some(v) => v
        None => Value::None
      }
      let fset = match get_named_value(inst.dict, "fset") {
        Some(v) => v
        None => Value::None
      }
      let doc = match get_named_value(inst.dict, "doc") {
        Some(v) => v
        None => Value::None
      }
      Ok(make_property_instance(inst.class, fget, fset, fdel, doc))
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "deleter() expects a property".to_string(),
        ),
      )
  }
}

///|
fn builtin_property_dunder_get(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("property.__get__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() < 2 || positional.length() > 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "property.__get__() takes 2 or 3 arguments".to_string(),
      ),
    )
  }
  let prop = positional[0]
  let obj = positional[1]
  let _owner = if positional.length() == 3 {
    positional[2]
  } else {
    Value::None
  }
  // Match CPython: accessing a property through the class returns the
  // descriptor itself.
  match obj {
    Value::None => return Ok(prop)
    _ => ()
  }
  match prop {
    Value::Instance(inst) => {
      let fget = match get_named_value(inst.dict, "fget") {
        Some(v) => v
        None => Value::None
      }
      match fget {
        Value::None =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Attribute,
              "AttributeError: unreadable attribute".to_string(),
            ),
          )
        _ => call_callable_with_env(fget, [obj], [], globals, builtins, io)
      }
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "property.__get__() expects a property".to_string(),
        ),
      )
  }
}

///|
fn builtin_staticmethod(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("staticmethod", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "staticmethod() takes exactly one argument".to_string(),
      ),
    )
  }
  let dict : Array[(String, Value)] = []
  dict.push(("func", positional[0]))
  let klass = builtin_class_from_name("staticmethod", builtins)
  Ok(Value::Instance(InstanceValue::{ class: klass, dict, }))
}

///|
fn builtin_staticmethod_get(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("staticmethod.__get__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__get__() takes exactly 2 arguments".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Instance(inst) =>
      if inst.class.name == "staticmethod" {
        match get_named_value(inst.dict, "func") {
          Some(v) => Ok(v)
          None => Ok(Value::None)
        }
      } else {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "staticmethod.__get__() expects a staticmethod".to_string(),
          ),
        )
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "staticmethod.__get__() expects a staticmethod".to_string(),
        ),
      )
  }
}

///|
fn builtin_classmethod(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("classmethod", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "classmethod() takes exactly one argument".to_string(),
      ),
    )
  }
  let dict : Array[(String, Value)] = []
  dict.push(("func", positional[0]))
  let klass = builtin_class_from_name("classmethod", builtins)
  Ok(Value::Instance(InstanceValue::{ class: klass, dict, }))
}

///|
fn builtin_classmethod_init(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = locals
  let _ = globals
  let _ = builtins
  let _ = io
  let _ = match ensure_no_keywords("classmethod.__init__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "classmethod.__init__() takes exactly two arguments".to_string(),
      ),
    )
  }
  let target = positional[0]
  let func_value = positional[1]
  match target {
    Value::Instance(inst) => {
      set_named_value(inst.dict, "func", func_value)
      Ok(Value::None)
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "classmethod.__init__() expects a classmethod instance".to_string(),
        ),
      )
  }
}

///|
fn builtin_staticmethod_init(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = locals
  let _ = globals
  let _ = builtins
  let _ = io
  let _ = match ensure_no_keywords("staticmethod.__init__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "staticmethod.__init__() takes exactly two arguments".to_string(),
      ),
    )
  }
  let target = positional[0]
  let func_value = positional[1]
  match target {
    Value::Instance(inst) => {
      set_named_value(inst.dict, "func", func_value)
      Ok(Value::None)
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "staticmethod.__init__() expects a staticmethod instance".to_string(),
        ),
      )
  }
}

///|
fn builtin_register(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("register", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "register() takes exactly two arguments".to_string(),
      ),
    )
  }
  let cls_value = positional[0]
  let subclass_value = positional[1]
  let subclass = match subclass_value {
    Value::Class(_) => subclass_value
    Value::Function(func) =>
      if func.body.length() == 0 && is_builtin_class_name(func.name) {
        Value::Class(builtin_class_from_name(func.name, builtins))
      } else {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "Can only register classes".to_string(),
          ),
        )
      }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "Can only register classes".to_string(),
        ),
      )
  }
  let cls = match cls_value {
    Value::Class(klass) => klass
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "register() expects a class".to_string(),
        ),
      )
  }
  let already_subclass = match
    issubclass_pair(subclass, Value::Class(cls), globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if already_subclass {
    return Ok(subclass)
  }
  let creates_cycle = match
    issubclass_pair(Value::Class(cls), subclass, globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if creates_cycle {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "Refusing to create an inheritance cycle".to_string(),
      ),
    )
  }
  let registry = match get_named_value(cls.dict, "_abc_registry") {
    Some(Value::Set(values)) => values
    _ => []
  }
  match set_add_unique(registry, subclass) {
    Ok(_) => ()
    Err(_) => ()
  }
  set_named_value(cls.dict, "_abc_registry", Value::Set(registry))
  let mro = match class_mro(cls) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  for base in mro {
    if base.name == cls.name {
      continue
    }
    let base_registry = match get_named_value(base.dict, "_abc_registry") {
      Some(Value::Set(values)) => values
      _ => []
    }
    match set_add_unique(base_registry, subclass) {
      Ok(_) => ()
      Err(_) => ()
    }
    set_named_value(base.dict, "_abc_registry", Value::Set(base_registry))
  }
  abc_cache_token_ref.val = abc_cache_token_ref.val + 1N
  Ok(subclass)
}

///|
fn builtin_mpython_abc_cache_token(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("__mpython_abc_cache_token", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__mpython_abc_cache_token() takes no arguments".to_string(),
      ),
    )
  }
  Ok(Value::Int(abc_cache_token_ref.val))
}

///|
fn builtin_mpython_abc_cache_token_bump(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("__mpython_abc_cache_token_bump", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__mpython_abc_cache_token_bump() takes no arguments".to_string(),
      ),
    )
  }
  abc_cache_token_ref.val = abc_cache_token_ref.val + 1N
  Ok(Value::Int(abc_cache_token_ref.val))
}

///|
fn builtin_bool(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("bool", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 {
    return Ok(Value::Bool(false))
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "bool() takes at most one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Instance(_) =>
      match truthy_from_value_with_env(positional[0], globals, builtins, io) {
        Ok(v) => Ok(Value::Bool(v))
        Err(err) => Err(err)
      }
    other => Ok(Value::Bool(bool_from_value(other)))
  }
}

///|
fn builtin_repr(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("repr", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "repr() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = positional[0]
  match value {
    Value::Str(text) => Ok(Value::Str(repr_string(text)))
    Value::Instance(inst) =>
      if is_base_exception_class_value(inst.class) {
        fn repr_arg(
          arg : Value,
          globals : Array[(String, Value)],
          builtins : Array[(String, Value)],
          io : MockIO,
        ) -> Result[String, RuntimeError] {
          match
            eval_builtin_call("repr", [arg], [], [], globals, builtins, io) {
            Ok(Some(Value::Str(text))) => Ok(text)
            Ok(Some(other)) => Ok(value_to_string(other))
            Ok(None) => Ok(value_to_string(arg))
            Err(err) => Err(err)
          }
        }

        let args : Array[Value] = match
          lookup_instance_field(inst.dict, "args") {
          Some(Value::Tuple(items)) => items
          Some(other) => [other]
          None => []
        }
        if args.length() == 0 {
          Ok(Value::Str(inst.class.name + "()"))
        } else if args.length() == 1 {
          let part = match repr_arg(args[0], globals, builtins, io) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          Ok(Value::Str(inst.class.name + "(" + part + ")"))
        } else {
          let buf = StringBuilder()
          buf.write_string(inst.class.name)
          buf.write_char('(')
          for i = 0; i < args.length(); i = i + 1 {
            if i > 0 {
              buf.write_string(", ")
            }
            let part = match repr_arg(args[i], globals, builtins, io) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            buf.write_string(part)
          }
          buf.write_char(')')
          Ok(Value::Str(buf.to_string()))
        }
      } else {
        match get_attr_from_value(value, "__repr__", globals, builtins, io) {
          Ok(callable) =>
            match callable {
              Value::BoundMethod(bound) =>
                if bound.function.body.length() > 0 {
                  match
                    (eval_function_with_kwargs_ref.val)(
                      bound.function,
                      [bound.self],
                      [],
                      globals,
                      builtins,
                      io,
                    ) {
                    Ok(Value::Str(text)) => Ok(Value::Str(text))
                    Ok(_) =>
                      Err(
                        make_runtime_error(
                          RuntimeErrorKind::Type,
                          "__repr__ returned non-string".to_string(),
                        ),
                      )
                    Err(err) => Err(err)
                  }
                } else {
                  Ok(Value::Str(repr_fallback(value)))
                }
              Value::Function(func) =>
                if func.body.length() > 0 {
                  match
                    (eval_function_with_kwargs_ref.val)(
                      func,
                      [value],
                      [],
                      globals,
                      builtins,
                      io,
                    ) {
                    Ok(Value::Str(text)) => Ok(Value::Str(text))
                    Ok(_) =>
                      Err(
                        make_runtime_error(
                          RuntimeErrorKind::Type,
                          "__repr__ returned non-string".to_string(),
                        ),
                      )
                    Err(err) => Err(err)
                  }
                } else {
                  Ok(Value::Str(repr_fallback(value)))
                }
              _ => Ok(Value::Str(repr_fallback(value)))
            }
          Err(err) =>
            match err.kind {
              RuntimeErrorKind::Attribute =>
                Ok(Value::Str(repr_fallback(value)))
              _ => Err(err)
            }
        }
      }
    _ => Ok(Value::Str(repr_fallback(value)))
  }
}

///|
fn builtin_ascii(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("ascii", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "ascii() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Str(text) => Ok(Value::Str(repr_string(text)))
    other => Ok(Value::Str(value_to_string(other)))
  }
}

///|
fn builtin_callable(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("callable", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "callable() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = match positional[0] {
    Value::Function(_) | Value::Class(_) | Value::BoundMethod(_) => true
    Value::Instance(inst) =>
      match lookup_class_attr(inst.class, "__call__") {
        Ok(Some(_)) => true
        Ok(None) => false
        Err(_) => false
      }
    _ => false
  }
  Ok(Value::Bool(value))
}

///|
fn value_matches_builtin_class(obj : Value, name : String) -> Bool {
  match name {
    "function" =>
      match obj {
        Value::Function(_) => true
        _ => false
      }
    "method" =>
      match obj {
        Value::BoundMethod(_) => true
        _ => false
      }
    "int" =>
      match obj {
        Value::Int(_) | Value::Bool(_) => true
        _ => false
      }
    "bool" =>
      match obj {
        Value::Bool(_) => true
        _ => false
      }
    "float" =>
      match obj {
        Value::Float(_) => true
        _ => false
      }
    "complex" =>
      match obj {
        Value::Complex(_, _) => true
        _ => false
      }
    "str" =>
      match obj {
        Value::Str(_) => true
        _ => false
      }
    "bytes" =>
      match obj {
        Value::Bytes(_) => true
        _ => false
      }
    "bytearray" =>
      match obj {
        Value::ByteArray(_) => true
        _ => false
      }
    "memoryview" =>
      match obj {
        Value::MemoryView(_) => true
        _ => false
      }
    "list" =>
      match obj {
        Value::List(_) => true
        _ => false
      }
    "tuple" =>
      match obj {
        Value::Tuple(_) => true
        _ => false
      }
    "dict" =>
      match obj {
        Value::Dict(_) => true
        _ => false
      }
    "set" =>
      match obj {
        Value::Set(_) => true
        _ => false
      }
    "NoneType" =>
      match obj {
        Value::None => true
        _ => false
      }
    _ => false
  }
}

///|
fn builtin_isinstance(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("isinstance", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "isinstance() takes exactly two arguments".to_string(),
      ),
    )
  }
  let obj = positional[0]
  let cls = positional[1]
  fn isinstance_single(obj : Value, cls : Value) -> Result[Bool, RuntimeError] {
    match cls {
      Value::Function(func) =>
        if func.body.length() == 0 {
          let ok = value_matches_builtin_class(obj, func.name)
          Ok(ok)
        } else {
          Ok(false)
        }
      Value::Class(klass) =>
        if klass.name == "type" {
          match obj {
            Value::Class(_) => Ok(true)
            _ => Ok(false)
          }
        } else if get_named_value(klass.dict, "_abc_registry") is Some(_) {
          let obj_cls = match obj {
            Value::Instance(inst) => Value::Class(inst.class)
            Value::Class(c) => Value::Class(c)
            _ =>
              Value::Class(
                builtin_class_from_name(type_name_from_value(obj), builtins),
              )
          }
          let ok = match
            issubclass_pair(obj_cls, Value::Class(klass), globals, builtins, io) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          Ok(ok)
        } else if value_matches_builtin_class(obj, klass.name) {
          Ok(true)
        } else {
          match obj {
            Value::Instance(inst) => {
              let mro = match class_mro(inst.class) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              for c in mro {
                if c.name == klass.name {
                  return Ok(true)
                }
              }
              Ok(false)
            }
            Value::Class(obj_class) =>
              // Class objects are instances of their metaclass. We track a
              // best-effort `__mpython_metaclass__` link for common stdlib needs.
              if klass.name == "object" {
                Ok(true)
              } else {
                let type_class = builtin_class_from_name("type", builtins)
                let meta_value = match
                  get_named_value(obj_class.dict, "__mpython_metaclass__") {
                  Some(Value::Class(meta)) => Value::Class(meta)
                  _ => Value::Class(type_class)
                }
                let ok = match
                  issubclass_pair(
                    meta_value,
                    Value::Class(klass),
                    globals,
                    builtins,
                    io,
                  ) {
                  Ok(v) => v
                  Err(err) => return Err(err)
                }
                Ok(ok)
              }
            _ => Ok(false)
          }
        }
      _ =>
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "isinstance() arg 2 must be a type or tuple of types".to_string(),
          ),
        )
    }
  }

  fn isinstance_multi(obj : Value, cls : Value) -> Result[Bool, RuntimeError] {
    match cls {
      Value::Tuple(values) => {
        for entry in values {
          let ok = match isinstance_multi(obj, entry) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          if ok {
            return Ok(true)
          }
        }
        Ok(false)
      }
      _ => isinstance_single(obj, cls)
    }
  }

  let ok = match isinstance_multi(obj, cls) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Bool(ok))
}

///|
fn issubclass_type_error_arg1() -> RuntimeError {
  make_runtime_error(
    RuntimeErrorKind::Type,
    "issubclass() arg 1 must be a class".to_string(),
  )
}

///|
fn issubclass_type_error_arg2() -> RuntimeError {
  make_runtime_error(
    RuntimeErrorKind::Type,
    "issubclass() arg 2 must be a class or tuple of classes".to_string(),
  )
}

///|
fn issubclass_matches_builtin_names(
  cls_name : String,
  base_name : String,
) -> Bool {
  if cls_name == base_name {
    true
  } else {
    cls_name == "bool" && base_name == "int"
  }
}

///|
fn is_not_implemented_value(value : Value) -> Bool {
  match value {
    Value::Instance(inst) => inst.class.name == "NotImplementedType"
    _ => false
  }
}

///|
fn issubclass_pair(
  cls : Value,
  base : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Bool, RuntimeError] {
  match base {
    Value::Tuple(values) => {
      for entry in values {
        let ok = match issubclass_pair(cls, entry, globals, builtins, io) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if ok {
          return Ok(true)
        }
      }
      Ok(false)
    }
    Value::Class(base_class) => {
      let subclass_hook = match
        get_attr_raw_from_value(
          Value::Class(base_class),
          "__subclasshook__",
          globals,
          builtins,
          io,
        ) {
        Ok(v) => Some(v)
        Err(err) =>
          match err.kind {
            RuntimeErrorKind::Attribute => None
            _ => return Err(err)
          }
      }
      if subclass_hook is Some(hook_value) {
        let result = match
          call_callable_with_env(hook_value, [cls], [], globals, builtins, io) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if !is_not_implemented_value(result) {
          return Ok(bool_from_value(result))
        }
      }
      // ABCMeta semantics: prefer the metaclass-provided `__subclasscheck__`
      // when this looks like an ABC. This handles virtual subclass registration
      // transitively (e.g. ExtensionFileLoader registered under ExecutionLoader
      // should also count as an InspectLoader).
      if get_named_value(base_class.dict, "_abc_registry") is Some(_) {
        let subclass_check = match
          get_attr_raw_from_value(
            Value::Class(base_class),
            "__subclasscheck__",
            globals,
            builtins,
            io,
          ) {
          Ok(v) => Some(v)
          Err(err) =>
            match err.kind {
              RuntimeErrorKind::Attribute => None
              _ => return Err(err)
            }
        }
        if subclass_check is Some(check_value) {
          let result = match
            call_callable_with_env(
              check_value,
              [cls],
              [],
              globals,
              builtins,
              io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          return Ok(bool_from_value(result))
        }
      }
      match get_named_value(base_class.dict, "_abc_registry") {
        Some(Value::Set(values)) =>
          for item in values {
            match item {
              Value::Class(klass) =>
                if klass.name == base_class.name {
                  return Ok(true)
                }
              _ => ()
            }
            let ok = match issubclass_pair(cls, item, globals, builtins, io) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            if ok {
              return Ok(true)
            }
          }
        _ => ()
      }
      if base_class.name == "type" {
        match cls {
          Value::Class(klass) => {
            let mro = match class_mro(klass) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            for c in mro {
              if c.name == "type" {
                return Ok(true)
              }
            }
            Ok(false)
          }
          Value::Function(func) =>
            // Builtin stubs: only `type` itself should be a subclass of `type`.
            Ok(func.body.length() == 0 && func.name == "type")
          _ => Ok(false)
        }
      } else {
        match cls {
          Value::Class(klass) => {
            let mro = match class_mro(klass) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            for c in mro {
              if c.name == base_class.name {
                return Ok(true)
              }
            }
            // If the base provides a custom __subclasses__ implementation,
            // validate it and use it as a fallback (ABCMeta does this).
            match get_named_value(base_class.dict, "__subclasses__") {
              Some(subclasses_value) => {
                let raw = match
                  call_callable_with_env(
                    subclasses_value,
                    [],
                    [],
                    globals,
                    builtins,
                    io,
                  ) {
                  Ok(v) => v
                  Err(err) => return Err(err)
                }
                let subclasses = match raw {
                  Value::List(values) => values
                  Value::Tuple(values) => values
                  _ =>
                    return Err(
                      make_runtime_error(
                        RuntimeErrorKind::Type,
                        "issubclass() arg 2 must be a class or tuple of classes".to_string(),
                      ),
                    )
                }
                for entry in subclasses {
                  match entry {
                    Value::Class(_) => {
                      let ok = match
                        issubclass_pair(cls, entry, globals, builtins, io) {
                        Ok(v) => v
                        Err(err) => return Err(err)
                      }
                      if ok {
                        return Ok(true)
                      }
                    }
                    _ =>
                      return Err(
                        make_runtime_error(
                          RuntimeErrorKind::Type,
                          "issubclass() arg 2 must be a class or tuple of classes".to_string(),
                        ),
                      )
                  }
                }
              }
              None => ()
            }
            Ok(false)
          }
          Value::Function(func) =>
            if func.body.length() == 0 {
              Ok(func.name == base_class.name)
            } else {
              Err(issubclass_type_error_arg1())
            }
          _ => Err(issubclass_type_error_arg1())
        }
      }
    }
    Value::Function(base_func) =>
      if base_func.body.length() == 0 {
        if base_func.name == "type" {
          match cls {
            Value::Class(klass) => {
              let mro = match class_mro(klass) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              for c in mro {
                if c.name == "type" {
                  return Ok(true)
                }
              }
              Ok(false)
            }
            Value::Function(func) =>
              Ok(func.body.length() == 0 && func.name == "type")
            _ => Ok(false)
          }
        } else {
          match cls {
            Value::Class(klass) => Ok(klass.name == base_func.name)
            Value::Function(func) =>
              if func.body.length() == 0 {
                Ok(issubclass_matches_builtin_names(func.name, base_func.name))
              } else {
                Err(issubclass_type_error_arg1())
              }
            _ => Err(issubclass_type_error_arg1())
          }
        }
      } else {
        Err(issubclass_type_error_arg2())
      }
    _ => Err(issubclass_type_error_arg2())
  }
}

///|
fn builtin_issubclass(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("issubclass", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "issubclass() takes exactly two arguments".to_string(),
      ),
    )
  }
  let cls = positional[0]
  let base = positional[1]
  let ok = match issubclass_pair(cls, base, globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Bool(ok))
}

///|
fn builtin_type_new(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  // `type.__new__` accepts arbitrary class keywords (`**kwds`) and ignores
  // unknown entries. These keywords are forwarded by metaclasses like
  // `abc.ABCMeta.__new__` and are later consumed by `__init_subclass__`.
  let _ = keywords
  if positional.length() != 4 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "type.__new__() takes exactly 4 arguments".to_string(),
      ),
    )
  }
  let name = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "type.__new__() name must be str".to_string(),
        ),
      )
  }
  let base_values : Array[Value] = []
  let raw_bases = match positional[2] {
    Value::Tuple(values) => values
    Value::List(values) => values
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "type.__new__() bases must be a tuple of classes".to_string(),
        ),
      )
  }
  for base in raw_bases {
    match base {
      Value::Class(_) => base_values.push(base)
      Value::Function(func) =>
        if func.body.length() == 0 && is_builtin_class_name(func.name) {
          base_values.push(
            Value::Class(builtin_class_from_name(func.name, builtins)),
          )
        } else {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "type.__new__() bases must be a tuple of classes".to_string(),
            ),
          )
        }
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "type.__new__() bases must be a tuple of classes".to_string(),
          ),
        )
    }
  }
  if base_values.length() == 0 {
    match get_named_value(builtins, "object") {
      Some(Value::Class(object_class)) =>
        base_values.push(Value::Class(object_class))
      _ => ()
    }
  }
  let class_dict : Array[(String, Value)] = []
  let pairs = match positional[3] {
    Value::Dict(values) => values
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "type.__new__() dict must be a dict".to_string(),
        ),
      )
  }
  for pair in pairs {
    match pair.0 {
      Value::Str(key) => class_dict.push((key, pair.1))
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "type.__new__() dict must have str keys".to_string(),
          ),
        )
    }
  }
  if get_named_value(class_dict, "__name__") is None {
    class_dict.push(("__name__", Value::Str(name)))
  }
  if get_named_value(class_dict, "__qualname__") is None {
    class_dict.push(("__qualname__", Value::Str(name)))
  }
  if get_named_value(class_dict, "__module__") is None {
    match get_named_value(globals, "__name__") {
      Some(Value::Str(module_name)) =>
        class_dict.push(("__module__", Value::Str(module_name)))
      _ => ()
    }
  }
  if get_named_value(class_dict, "hashvalue") is None {
    class_dict.push(("hashvalue", Value::Int(fresh_object_hashvalue())))
  }
  let klass = ClassValue::{ name, bases: base_values, dict: class_dict, }
  // Track the metaclass used to construct this class (`type.__new__(mcls, ...)`)
  // so `type(C)` and `isinstance(C, mcls)` behave like CPython.
  match positional[0] {
    Value::Class(meta) =>
      set_named_value(klass.dict, "__mpython_metaclass__", Value::Class(meta))
    _ => ()
  }
  let _ = match class_mro(klass) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  // Track direct subclass relationships for `type.__subclasses__()`.
  let sub_value = Value::Class(klass)
  for base in base_values {
    match base {
      Value::Class(base_class) =>
        match get_named_value(base_class.dict, "__mpython_subclasses__") {
          Some(Value::List(values)) => values.push(sub_value)
          _ =>
            base_class.dict.push(
              ("__mpython_subclasses__", Value::List([sub_value])),
            )
        }
      _ => ()
    }
  }
  // PEP 487: call inherited __init_subclass__ hooks.
  match class_mro(klass) {
    Ok(mro) => {
      let mut hook : Value? = None
      for i = 1; i < mro.length(); i = i + 1 {
        match get_named_value(mro[i].dict, "__init_subclass__") {
          Some(Value::Function(func)) => {
            hook = Some(
              Value::BoundMethod(BoundMethodValue::{
                function: func,
                self: Value::Class(klass),
              }),
            )
            break
          }
          _ => ()
        }
      }
      match hook {
        Some(callable) => {
          let _ = match
            call_callable_with_env(
              callable,
              [],
              keywords,
              globals,
              builtins,
              io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
        }
        None => ()
      }
    }
    Err(_) => ()
  }
  Ok(Value::Class(klass))
}

///|
fn builtin_sys_getrefcount(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sys.getrefcount", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "getrefcount() takes exactly one argument".to_string(),
      ),
    )
  }
  // Moonpython doesn't expose real refcounts; return a stable placeholder.
  Ok(Value::Int(3N))
}

///|
fn builtin_type(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  if positional.length() == 1 {
    let value = positional[0]
    return match value {
      Value::Instance(inst) => Ok(Value::Class(inst.class))
      Value::Class(klass) =>
        match get_named_value(klass.dict, "__mpython_metaclass__") {
          Some(Value::Class(meta)) => Ok(Value::Class(meta))
          _ => Ok(Value::Class(builtin_class_from_name("type", builtins)))
        }
      _ =>
        Ok(
          Value::Class(
            builtin_class_from_name(type_name_from_value(value), builtins),
          ),
        )
    }
  }
  if positional.length() != 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "type() takes 1 or 3 arguments".to_string(),
      ),
    )
  }
  let name = match positional[0] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "type() argument 1 must be str".to_string(),
        ),
      )
  }
  let base_values : Array[Value] = []
  let raw_bases = match positional[1] {
    Value::Tuple(values) => values
    Value::List(values) => values
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "type() argument 2 must be a tuple of classes".to_string(),
        ),
      )
  }
  for base in raw_bases {
    match base {
      Value::Class(_) => base_values.push(base)
      Value::Function(func) =>
        if func.body.length() == 0 && is_builtin_class_name(func.name) {
          base_values.push(
            Value::Class(builtin_class_from_name(func.name, builtins)),
          )
        } else {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "type() argument 2 must be a tuple of classes".to_string(),
            ),
          )
        }
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "type() argument 2 must be a tuple of classes".to_string(),
          ),
        )
    }
  }
  if base_values.length() == 0 {
    match get_named_value(builtins, "object") {
      Some(Value::Class(object_class)) =>
        base_values.push(Value::Class(object_class))
      _ => ()
    }
  }
  let class_dict : Array[(String, Value)] = []
  let pairs = match positional[2] {
    Value::Dict(values) => values
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "type() argument 3 must be a dict".to_string(),
        ),
      )
  }
  for pair in pairs {
    match pair.0 {
      Value::Str(key) => class_dict.push((key, pair.1))
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "type() argument 3 must be a dict with str keys".to_string(),
          ),
        )
    }
  }
  if get_named_value(class_dict, "__name__") is None {
    class_dict.push(("__name__", Value::Str(name)))
  }
  if get_named_value(class_dict, "__qualname__") is None {
    class_dict.push(("__qualname__", Value::Str(name)))
  }
  if get_named_value(class_dict, "__module__") is None {
    match get_named_value(globals, "__name__") {
      Some(Value::Str(module_name)) =>
        class_dict.push(("__module__", Value::Str(module_name)))
      _ => ()
    }
  }
  if get_named_value(class_dict, "hashvalue") is None {
    class_dict.push(("hashvalue", Value::Int(fresh_object_hashvalue())))
  }
  let klass = ClassValue::{ name, bases: base_values, dict: class_dict, }
  let _ = match class_mro(klass) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  // Track direct subclass relationships for `type.__subclasses__()`.
  let sub_value = Value::Class(klass)
  for base in base_values {
    match base {
      Value::Class(base_class) =>
        match get_named_value(base_class.dict, "__mpython_subclasses__") {
          Some(Value::List(values)) => values.push(sub_value)
          _ =>
            base_class.dict.push(
              ("__mpython_subclasses__", Value::List([sub_value])),
            )
        }
      _ => ()
    }
  }
  // PEP 487: call inherited __init_subclass__ hooks.
  match class_mro(klass) {
    Ok(mro) => {
      let mut hook : Value? = None
      for i = 1; i < mro.length(); i = i + 1 {
        match get_named_value(mro[i].dict, "__init_subclass__") {
          Some(Value::Function(func)) => {
            hook = Some(
              Value::BoundMethod(BoundMethodValue::{
                function: func,
                self: Value::Class(klass),
              }),
            )
            break
          }
          _ => ()
        }
      }
      match hook {
        Some(callable) => {
          let _ = match
            call_callable_with_env(
              callable,
              [],
              keywords,
              globals,
              builtins,
              io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
        }
        None => ()
      }
    }
    Err(_) => ()
  }
  Ok(Value::Class(klass))
}

///|
fn builtin_id(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("id", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "id() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = positional[0]
  match value {
    // For immediate values, synthesize an id with a small type tag to avoid
    // collisions between e.g. 0 and False.
    Value::Int(v) => Ok(Value::Int(v * 8N + 1N))
    Value::Bool(v) => Ok(Value::Int((if v { 1N } else { 0N }) * 8N + 2N))
    Value::Float(v) =>
      Ok(Value::Int(@bigint.BigInt::from_int64(v.to_int64()) * 8N + 3N))
    Value::Complex(real, imag) =>
      Ok(
        Value::Int(
          @bigint.BigInt::from_int64(hash_complex(real, imag)) * 8N + 4N,
        ),
      )

    // Reference-backed values: use registries keyed by reference identity.
    Value::Str(text) => Ok(Value::Int(id_for_string(text)))
    Value::Bytes(values) => Ok(Value::Int(id_for_int_array(values)))
    Value::ByteArray(values) => Ok(Value::Int(id_for_int_array(values)))
    Value::MemoryView(values) => Ok(Value::Int(id_for_int_array(values)))
    Value::List(values) => Ok(Value::Int(id_for_value_array(values)))
    Value::Tuple(values) => Ok(Value::Int(id_for_value_array(values)))
    Value::Dict(pairs) => Ok(Value::Int(id_for_dict_pairs(pairs)))
    Value::Set(values) => Ok(Value::Int(id_for_value_array(values)))
    Value::Function(func) => Ok(Value::Int(function_identity_hash(func)))
    Value::Class(klass) =>
      match class_identity_hash(klass) {
        Some(v) => Ok(Value::Int(v))
        None => {
          let id = fresh_object_hashvalue()
          // Give each class a stable identity so tools like `pickle` can
          // memoize them without collisions.
          klass.dict.push(("hashvalue", Value::Int(id)))
          Ok(Value::Int(id))
        }
      }
    Value::Instance(inst) =>
      match get_named_value(inst.dict, "hashvalue") {
        Some(Value::Int(id)) => Ok(Value::Int(id))
        _ =>
          Ok(
            Value::Int(
              @bigint.BigInt::from_int64(
                hash_string(
                  type_name_from_value(value) + ":" + value_to_string(value),
                ),
              ),
            ),
          )
      }
    _ =>
      Ok(
        Value::Int(
          @bigint.BigInt::from_int64(
            hash_string(
              type_name_from_value(value) + ":" + value_to_string(value),
            ),
          ),
        ),
      )
  }
}

///|
fn builtin_hash(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("hash", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "hash() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = positional[0]
  // Prefer an explicit `__hash__` implementation when present (e.g. unittest.TestCase).
  if value is Value::Instance(_) || value is Value::Class(_) {
    match get_attr_from_value(value, "__hash__", globals, builtins, io) {
      Ok(hash_fn) =>
        if hash_fn is Value::None {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "unhashable type: '" + type_name_from_value(value) + "'",
            ),
          )
        } else {
          let result = match
            call_callable_with_env(hash_fn, [], [], globals, builtins, io) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          match result {
            Value::Int(v) => return Ok(Value::Int(v))
            Value::Bool(v) => return Ok(Value::Int(if v { 1N } else { 0N }))
            Value::Instance(inst) =>
              match get_named_value(inst.dict, int_storage_name) {
                Some(Value::Int(v)) => return Ok(Value::Int(v))
                Some(Value::Bool(v)) =>
                  return Ok(Value::Int(if v { 1N } else { 0N }))
                _ => ()
              }
            _ =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "__hash__() should return an int".to_string(),
                ),
              )
          }
        }
      Err(err) =>
        match err.kind {
          RuntimeErrorKind::Attribute => ()
          _ => return Err(err)
        }
    }
  }
  match hash_value(value) {
    Ok(hash) => Ok(Value::Int(hash))
    Err(err) => Err(err)
  }
}

///|
fn builtin_globals(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = io
  let _ = locals
  let _ = builtins
  let _ = match ensure_no_keywords("globals", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "globals() takes no arguments".to_string(),
      ),
    )
  }
  Ok(make_module_globals_dict_instance(globals))
}

///|
fn module_globals_env_from_self(
  name : String,
  self_value : Value,
) -> Result[Array[(String, Value)], RuntimeError] {
  match self_value {
    Value::Instance(inst) =>
      match get_named_value(inst.dict, "$__env_id__") {
        Some(Value::Int(id)) =>
          match lookup_env_mapping(id) {
            Some(env) => Ok(env)
            None =>
              Err(
                make_runtime_error(
                  RuntimeErrorKind::Runtime,
                  name + ": stale globals mapping".to_string(),
                ),
              )
          }
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              name + ": invalid globals mapping".to_string(),
            ),
          )
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          name + ": invalid globals mapping".to_string(),
        ),
      )
  }
}

///|
fn builtin_module_globals_getitem(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.__getitem__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__getitem__() takes exactly two arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.__getitem__", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match positional[1] {
    Value::Str(name) =>
      match get_global_value(env, name) {
        Some(v) => Ok(v)
        None =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Key,
              "key not found".to_string(),
            ),
          )
      }
    _ =>
      Err(
        make_runtime_error(RuntimeErrorKind::Key, "key not found".to_string()),
      )
  }
}

///|
fn builtin_module_globals_setitem(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.__setitem__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__setitem__() takes exactly three arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.__setitem__", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match positional[1] {
    Value::Str(name) => {
      set_global_value(env, name, positional[2])
      Ok(Value::None)
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "globals key must be str".to_string(),
        ),
      )
  }
}

///|
fn builtin_module_globals_delitem(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.__delitem__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__delitem__() takes exactly two arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.__delitem__", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match positional[1] {
    Value::Str(name) => {
      delete_global_value(env, name)
      Ok(Value::None)
    }
    _ =>
      Err(
        make_runtime_error(RuntimeErrorKind::Key, "key not found".to_string()),
      )
  }
}

///|
fn builtin_module_globals_len(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.__len__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__len__() takes exactly one argument".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.__len__", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Int(@bigint.BigInt::from_int(env.length())))
}

///|
fn builtin_module_globals_iter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.__iter__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__iter__() takes exactly one argument".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.__iter__", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let keys : Array[Value] = []
  for pair in env {
    keys.push(Value::Str(pair.0))
  }
  Ok(make_iterator(keys))
}

///|
fn builtin_module_globals_contains(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.__contains__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__contains__() takes exactly two arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.__contains__", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match positional[1] {
    Value::Str(name) => Ok(Value::Bool(get_global_value(env, name) is Some(_)))
    _ => Ok(Value::Bool(false))
  }
}

///|
fn builtin_module_globals_get(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.get", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() < 2 || positional.length() > 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "get() takes 2 or 3 arguments".to_string(),
      ),
    )
  }
  let default_value = if positional.length() == 3 {
    positional[2]
  } else {
    Value::None
  }
  let env = match
    module_globals_env_from_self("module_globals.get", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match positional[1] {
    Value::Str(name) =>
      if name == "hashvalue" {
        Ok(default_value)
      } else {
        match get_global_value(env, name) {
          Some(v) => Ok(v)
          None => Ok(default_value)
        }
      }
    _ => Ok(default_value)
  }
}

///|
fn builtin_module_globals_keys(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.keys", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "keys() takes no arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.keys", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let pairs : Array[(Value, Value)] = []
  for pair in env {
    if pair.0 == "hashvalue" {
      continue
    }
    pairs.push((Value::Str(pair.0), pair.1))
  }
  Ok(make_dict_view_instance("dict_keys", Value::Dict(pairs)))
}

///|
fn builtin_module_globals_values(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.values", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "values() takes no arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.values", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let pairs : Array[(Value, Value)] = []
  for pair in env {
    if pair.0 == "hashvalue" {
      continue
    }
    pairs.push((Value::Str(pair.0), pair.1))
  }
  Ok(make_dict_view_instance("dict_values", Value::Dict(pairs)))
}

///|
fn builtin_module_globals_items(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.items", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "items() takes no arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.items", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let pairs : Array[(Value, Value)] = []
  for pair in env {
    if pair.0 == "hashvalue" {
      continue
    }
    pairs.push((Value::Str(pair.0), pair.1))
  }
  Ok(make_dict_view_instance("dict_items", Value::Dict(pairs)))
}

///|
fn builtin_module_globals_pop(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.pop", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() < 2 || positional.length() > 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "pop() takes 2 or 3 arguments".to_string(),
      ),
    )
  }
  let default_value : Value? = if positional.length() == 3 {
    Some(positional[2])
  } else {
    None
  }
  let env = match
    module_globals_env_from_self("module_globals.pop", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match positional[1] {
    Value::Str(name) =>
      match get_global_value(env, name) {
        Some(v) => {
          delete_global_value(env, name)
          Ok(v)
        }
        None =>
          match default_value {
            Some(v) => Ok(v)
            None =>
              Err(
                make_runtime_error(
                  RuntimeErrorKind::Key,
                  "key not found".to_string(),
                ),
              )
          }
      }
    _ =>
      match default_value {
        Some(v) => Ok(v)
        None =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Key,
              "key not found".to_string(),
            ),
          )
      }
  }
}

///|
fn builtin_module_globals_update(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.update", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "update() takes at most one argument".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.update", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 1 {
    return Ok(Value::None)
  }
  let other = positional[1]
  match other {
    Value::None => return Ok(Value::None)
    _ => ()
  }
  match other {
    Value::Dict(pairs) =>
      for pair in pairs {
        match pair.0 {
          Value::Str(name) => set_global_value(env, name, pair.1)
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "globals key must be str".to_string(),
              ),
            )
        }
      }
    Value::Instance(inst) =>
      if class_includes_dict(inst.class) {
        let pairs = match get_named_value(inst.dict, dict_storage_name) {
          Some(Value::Dict(pairs)) => pairs
          Some(_) =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "update() expects a dict".to_string(),
              ),
            )
          None => {
            let pairs : Array[(Value, Value)] = []
            set_named_value(inst.dict, dict_storage_name, Value::Dict(pairs))
            pairs
          }
        }
        for pair in pairs {
          match pair.0 {
            Value::Str(name) => set_global_value(env, name, pair.1)
            _ =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "globals key must be str".to_string(),
                ),
              )
          }
        }
      } else {
        let iterator = match
          iter_value_to_iterator(other, globals, builtins, io) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        while true {
          let item = match
            iterator_next(iterator, None, globals, builtins, io) {
            Ok(v) => v
            Err(err) =>
              if err.exc_type == "StopIteration" {
                break
              } else {
                return Err(err)
              }
          }
          match item {
            Value::Tuple(values) | Value::List(values) =>
              if values.length() != 2 {
                return Err(
                  make_runtime_error(
                    RuntimeErrorKind::Type,
                    "update() sequence element has wrong length".to_string(),
                  ),
                )
              } else {
                match values[0] {
                  Value::Str(name) => set_global_value(env, name, values[1])
                  _ =>
                    return Err(
                      make_runtime_error(
                        RuntimeErrorKind::Type,
                        "globals key must be str".to_string(),
                      ),
                    )
                }
              }
            _ =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "update() expects (key, value) pairs".to_string(),
                ),
              )
          }
        }
      }
    _ => {
      let iterator = match
        iter_value_to_iterator(other, globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      while true {
        let item = match iterator_next(iterator, None, globals, builtins, io) {
          Ok(v) => v
          Err(err) =>
            if err.exc_type == "StopIteration" {
              break
            } else {
              return Err(err)
            }
        }
        match item {
          Value::Tuple(values) | Value::List(values) =>
            if values.length() != 2 {
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "update() sequence element has wrong length".to_string(),
                ),
              )
            } else {
              match values[0] {
                Value::Str(name) => set_global_value(env, name, values[1])
                _ =>
                  return Err(
                    make_runtime_error(
                      RuntimeErrorKind::Type,
                      "globals key must be str".to_string(),
                    ),
                  )
              }
            }
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "update() expects (key, value) pairs".to_string(),
              ),
            )
        }
      }
    }
  }
  Ok(Value::None)
}

///|
fn builtin_module_globals_copy(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("module_globals.copy", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "copy() takes no arguments".to_string(),
      ),
    )
  }
  let env = match
    module_globals_env_from_self("module_globals.copy", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let pairs : Array[(Value, Value)] = []
  for item in env {
    if item.0 == "hashvalue" {
      continue
    }
    pairs.push((Value::Str(item.0), item.1))
  }
  Ok(Value::Dict(pairs))
}

///|
fn builtin_vars(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("vars", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 {
    // vars() -> locals()
    let pairs : Array[(Value, Value)] = []
    for pair in locals {
      pairs.push((Value::Str(pair.0), pair.1))
    }
    return Ok(Value::Dict(pairs))
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "vars() takes at most one argument".to_string(),
      ),
    )
  }
  match get_attr_from_value(positional[0], "__dict__", globals, builtins, io) {
    Ok(v) => Ok(v)
    Err(err) =>
      match err.kind {
        RuntimeErrorKind::Attribute =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "vars() argument must have __dict__ attribute".to_string(),
            ),
          )
        _ => Err(err)
      }
  }
}

///|
fn builtin_locals(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = io
  let _ = globals
  let _ = builtins
  let _ = match ensure_no_keywords("locals", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "locals() takes no arguments".to_string(),
      ),
    )
  }
  let pairs : Array[(Value, Value)] = []
  for pair in locals {
    pairs.push((Value::Str(pair.0), pair.1))
  }
  Ok(Value::Dict(pairs))
}

///|
fn builtin_eval(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let (expr_value, globals_arg, locals_arg) = match
    parse_exec_eval_args("eval", positional, keywords, "expression") {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let (expr_text, filename, mode) = match expr_value {
    Value::Str(text) => (text, "".to_string(), "eval".to_string())
    Value::Instance(inst) if inst.class.name == "code" => {
      let source = match get_named_value(inst.dict, "__mpython_source__") {
        Some(Value::Str(text)) => text
        _ =>
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "eval() code object missing source".to_string(),
            ),
          )
      }
      let filename = match get_named_value(inst.dict, "co_filename") {
        Some(Value::Str(text)) => text
        _ => "".to_string()
      }
      let mode = match get_named_value(inst.dict, "__mpython_mode__") {
        Some(Value::Str(text)) => text
        _ => "eval".to_string()
      }
      (source, filename, mode)
    }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "eval() arg 1 must be a string".to_string(),
        ),
      )
  }
  if mode != "eval" {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "eval() code object must be created with mode 'eval'".to_string(),
      ),
    )
  }
  let (locals_env, globals_env, locals_pairs, globals_pairs) = match
    resolve_exec_eval_env("eval", globals_arg, locals_arg, locals, globals) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let expr = match parse_expr(expr_text) {
    Ok(v) => v
    Err(err) => return Err(make_parse_runtime_error(err))
  }
  let _ = match ensure_recursion_available() {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  push_traceback_frame("".to_string(), filename)
  let result = eval_expr_with_env(expr, locals_env, globals_env, builtins, io)
  pop_traceback_frame()
  match globals_pairs {
    Some(pairs) => sync_env_to_dict(globals_env, pairs)
    None => ()
  }
  match locals_pairs {
    Some(pairs) => sync_env_to_dict(locals_env, pairs)
    None => ()
  }
  result
}

///|
fn builtin_exec(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let (source_value, globals_arg, locals_arg) = match
    parse_exec_eval_args("exec", positional, keywords, "source") {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let (source_text, filename, mode) = match source_value {
    Value::Str(text) => (text, "".to_string(), "exec".to_string())
    Value::Instance(inst) if inst.class.name == "code" => {
      let source = match get_named_value(inst.dict, "__mpython_source__") {
        Some(Value::Str(text)) => text
        _ =>
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "exec() code object missing source".to_string(),
            ),
          )
      }
      let filename = match get_named_value(inst.dict, "co_filename") {
        Some(Value::Str(text)) => text
        _ => "".to_string()
      }
      let mode = match get_named_value(inst.dict, "__mpython_mode__") {
        Some(Value::Str(text)) => text
        _ => "exec".to_string()
      }
      (source, filename, mode)
    }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "exec() arg 1 must be a string".to_string(),
        ),
      )
  }
  if mode != "exec" && mode != "single" {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "exec() code object must be created with mode 'exec' or 'single'".to_string(),
      ),
    )
  }
  let (locals_env, globals_env, locals_pairs, globals_pairs) = match
    resolve_exec_eval_env("exec", globals_arg, locals_arg, locals, globals) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let program = match parse(source_text) {
    Ok(v) => v
    Err(err) =>
      return Err(
        parse_error_as_exception_runtime_error(
          err, source_text, filename, globals_env, builtins, io,
        ),
      )
  }
  let _ = match ensure_recursion_available() {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  // `exec()` runs code objects with `co_name == ""` (even though the
  // compilation mode is "exec"/"single"), so report frames as "".
  push_traceback_frame("".to_string(), filename)
  let result = eval_block(
    program.body,
    locals_env,
    globals_env,
    builtins,
    io,
    current_config(),
  )
  pop_traceback_frame()
  match globals_pairs {
    Some(pairs) => sync_env_to_dict(globals_env, pairs)
    None => ()
  }
  match locals_pairs {
    Some(pairs) => sync_env_to_dict(locals_env, pairs)
    None => ()
  }
  match result {
    Ok(_) => Ok(Value::None)
    Err(err) => Err(err)
  }
}

///|
fn parse_error_as_exception_runtime_error(
  err : ParseError,
  source_text : String,
  filename : String,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> RuntimeError {
  let exc_name = match err.kind {
    ParseErrorKind::Indentation => "IndentationError".to_string()
    _ => "SyntaxError".to_string()
  }
  let msg = err.message
  let mut text_value : Value = Value::None
  let mut offset_value : Value = Value::Int(
    @bigint.BigInt::from_int(err.span.column),
  )
  let mut end_offset_value : Value = offset_value
  let lines : Array[String] = []
  for line in source_text.split("\n") {
    lines.push(line.to_owned())
  }
  if err.span.line > 0 && err.span.line <= lines.length() {
    let line = lines[err.span.line - 1]
    text_value = Value::Str(line)
    // When we have a span range on a single line (e.g. generator expression
    // errors), provide end_offset so traceback can render a caret range.
    match offset_value {
      Value::Int(v) =>
        match bigint_to_int_checked(v) {
          Ok(start_col) => {
            let mut end_col = start_col + (err.span.end - err.span.start)
            if end_col < start_col {
              end_col = start_col
            }
            if end_col > line.length() + 1 {
              end_col = line.length() + 1
            }
            end_offset_value = Value::Int(@bigint.BigInt::from_int(end_col))
          }
          Err(_) => ()
        }
      _ => ()
    }
    if exc_name == "IndentationError".to_string() &&
      msg != "unexpected indent".to_string() {
      // CPython points indentation-related carets at the end of the line.
      offset_value = Value::Int(@bigint.BigInt::from_int(line.length() + 1))
      end_offset_value = offset_value
    }
  }
  if exc_name == "IndentationError".to_string() &&
    msg == "unexpected indent".to_string() {
    // CPython omits the caret line for "unexpected indent".
    offset_value = Value::None
    end_offset_value = Value::None
  }
  let loc_tuple = Value::Tuple([
    Value::Str(filename),
    Value::Int(@bigint.BigInt::from_int(err.span.line)),
    offset_value,
    text_value,
    Value::Int(@bigint.BigInt::from_int(err.span.line)),
    end_offset_value,
  ])
  let inst = match get_named_value(builtins, exc_name) {
    Some(Value::Class(klass)) =>
      match
        call_callable_with_env(
          Value::Class(klass),
          [Value::Str(msg), loc_tuple],
          [],
          globals,
          builtins,
          io,
        ) {
        Ok(v) => v
        Err(e) =>
          return make_runtime_error(
            RuntimeErrorKind::Runtime,
            "RuntimeError: " + e.message,
          )
      }
    _ =>
      return make_runtime_error(
        RuntimeErrorKind::Runtime,
        exc_name + ": " + msg,
      )
  }
  RuntimeError::{
    kind: RuntimeErrorKind::Runtime,
    message: exc_name + ": " + msg,
    span: Some(err.span),
    // Preserve the call stack leading to the failed compile/parse so
    // `traceback.format_exc()` can display where the SyntaxError originated.
    traceback: snapshot_traceback(),
    traceback_envs: snapshot_frame_env_stack(),
    exc_type: exc_name,
    exc_args: [msg],
    exc_value: Some(inst),
    exc_cause: None,
    exc_context: None,
    exc_suppress_context: false,
  }
}

///|
fn builtin_compile(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = keywords
  // Accept keyword arguments (flags/dont_inherit/optimize) but ignore them for now.
  // A number of stdlib helpers call `compile(..., dont_inherit=True)`.
  if positional.length() < 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "compile() missing required arguments".to_string(),
      ),
    )
  }
  let source_text = match positional[0] {
    Value::Str(text) => text
    Value::Bytes(bytes) =>
      match decode_bytes_utf8("compile", bytes, "surrogateescape") {
        Ok(v) => v
        Err(err) => return Err(err)
      }
    Value::ByteArray(bytes) =>
      match decode_bytes_utf8("compile", bytes, "surrogateescape") {
        Ok(v) => v
        Err(err) => return Err(err)
      }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "compile() arg 1 must be a string or bytes-like object".to_string(),
        ),
      )
  }
  let filename = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "compile() arg 2 must be a string".to_string(),
        ),
      )
  }
  let mode = match positional[2] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "compile() arg 3 must be a string".to_string(),
        ),
      )
  }
  if mode == "eval" {
    let _ = match parse_expr(source_text) {
      Ok(v) => v
      Err(err) =>
        return Err(
          parse_error_as_exception_runtime_error(
            err, source_text, filename, globals, builtins, io,
          ),
        )
    }
  } else if mode == "exec" || mode == "single" {
    let _ = match parse(source_text) {
      Ok(v) => v
      Err(err) =>
        return Err(
          parse_error_as_exception_runtime_error(
            err, source_text, filename, globals, builtins, io,
          ),
        )
    }
  } else {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "compile() mode must be 'exec', 'eval' or 'single'".to_string(),
      ),
    )
  }

  // Minimal CodeType placeholder: carry enough metadata for stdlib callers
  // (importlib, inspect) to access `co_filename`, and let `exec`/`eval` consume it.
  let code = make_code_instance_with_meta(
    0N,
    filename,
    "".to_string(),
    1N,
    0,
    0,
    0,
    [],
  )
  match code {
    Value::Instance(inst) => {
      inst.dict.push(("__mpython_source__", Value::Str(source_text)))
      inst.dict.push(("__mpython_mode__", Value::Str(mode)))
      Ok(code)
    }
    _ => Ok(code)
  }
}

///|
fn builtin_import(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = locals
  if positional.length() > 5 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__import__() takes at most 5 arguments (" +
        positional.length().to_string() +
        " given)",
      ),
    )
  }

  // Signature: __import__(name, globals=None, locals=None, fromlist=(), level=0)
  let mut name_value : Value? = None
  let mut globals_value = Value::None
  let mut locals_value = Value::None
  let mut fromlist_value : Value = Value::Tuple([])
  let mut level_value : Value = Value::Int(0N)
  if positional.length() >= 1 {
    name_value = Some(positional[0])
  }
  if positional.length() >= 2 {
    globals_value = positional[1]
  }
  if positional.length() >= 3 {
    locals_value = positional[2]
  }
  if positional.length() >= 4 {
    fromlist_value = positional[3]
  }
  if positional.length() == 5 {
    level_value = positional[4]
  }
  for pair in keywords {
    let key = pair.0
    let value = pair.1
    match key {
      "name" =>
        if name_value is Some(_) || positional.length() >= 1 {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "__import__() got multiple values for argument 'name'".to_string(),
            ),
          )
        } else {
          name_value = Some(value)
        }
      "globals" =>
        if positional.length() >= 2 {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "__import__() got multiple values for argument 'globals'".to_string(),
            ),
          )
        } else {
          globals_value = value
        }
      "locals" =>
        if positional.length() >= 3 {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "__import__() got multiple values for argument 'locals'".to_string(),
            ),
          )
        } else {
          locals_value = value
        }
      "fromlist" =>
        if positional.length() >= 4 {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "__import__() got multiple values for argument 'fromlist'".to_string(),
            ),
          )
        } else {
          fromlist_value = value
        }
      "level" =>
        if positional.length() >= 5 {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "__import__() got multiple values for argument 'level'".to_string(),
            ),
          )
        } else {
          level_value = value
        }
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "__import__() got an unexpected keyword argument '" + key + "'",
          ),
        )
    }
  }

  // Keep compatibility with existing call sites: we don't use `globals/locals`
  // yet, but accept them to match CPython's signature.
  let _ = globals_value
  let _ = locals_value
  let name = match name_value {
    Some(Value::Str(text)) => text
    Some(_) =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "__import__() name must be str".to_string(),
        ),
      )
    None => return Err(missing_required_argument_error("__import__", "name"))
  }
  if name == "" {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: Empty module name".to_string(),
      ),
    )
  }
  match level_value {
    Value::None => ()
    Value::Int(v) =>
      match bigint_to_int_checked(v) {
        Ok(level) =>
          if level != 0 {
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "ImportError: relative import level not supported".to_string(),
              ),
            )
          }
        Err(err) => return Err(err)
      }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "__import__() level must be int".to_string(),
        ),
      )
  }
  let module_value = match
    import_module(name, globals, builtins, io, current_config()) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let mut has_fromlist = false
  match fromlist_value {
    Value::None => ()
    Value::List(values) => has_fromlist = values.length() > 0
    Value::Tuple(values) => has_fromlist = values.length() > 0
    _ => has_fromlist = true
  }
  if has_fromlist || !name.contains(".") {
    return Ok(module_value)
  }
  let top_name = module_top_name(name)
  match module_cache_get(top_name) {
    Some(value) => Ok(value)
    None => Ok(module_value)
  }
}

///|
fn builtin_dir(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = io
  let _ = match ensure_no_keywords("dir", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  fn str_lex_cmp(a : String, b : String) -> Int32 {
    if a == b {
      return 0
    }
    let aa = a.to_array()
    let bb = b.to_array()
    let mut i = 0
    while i < aa.length() && i < bb.length() {
      let ai = aa[i].to_int()
      let bi = bb[i].to_int()
      if ai < bi {
        return -1
      }
      if ai > bi {
        return 1
      }
      i = i + 1
    }
    if aa.length() < bb.length() {
      -1
    } else {
      1
    }
  }

  fn lex_sort_strings_in_place(items : Array[String]) -> Unit {
    fn swap(items : Array[String], i : Int32, j : Int32) -> Unit {
      let tmp = items[i]
      items[i] = items[j]
      items[j] = tmp
    }

    fn sort_range(items : Array[String], lo : Int32, hi : Int32) -> Unit {
      if lo >= hi {
        return
      }
      let pivot = items[(lo + hi) / 2]
      let mut i = lo
      let mut j = hi
      while i <= j {
        while str_lex_cmp(items[i], pivot) < 0 {
          i = i + 1
        }
        while str_lex_cmp(items[j], pivot) > 0 {
          j = j - 1
        }
        if i <= j {
          swap(items, i, j)
          i = i + 1
          j = j - 1
        }
      }
      if lo < j {
        sort_range(items, lo, j)
      }
      if i < hi {
        sort_range(items, i, hi)
      }
    }

    if items.length() <= 1 {
      return
    }
    sort_range(items, 0, items.length() - 1)
  }

  fn maybe_sort_str_list(items : Array[Value]) -> Array[Value] {
    let strs : Array[String] = []
    for item in items {
      match item {
        Value::Str(text) => strs.push(text)
        _ => return items
      }
    }
    lex_sort_strings_in_place(strs)
    let out : Array[Value] = []
    for s in strs {
      out.push(Value::Str(s))
    }
    out
  }

  if positional.length() == 0 {
    let names : Array[Value] = []
    for pair in globals {
      names.push(Value::Str(pair.0))
    }
    return Ok(Value::List(maybe_sort_str_list(names)))
  }
  let _ = builtins
  let _ = locals
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "dir() takes 0 or 1 arguments".to_string(),
      ),
    )
  }
  let value = positional[0]

  // __dir__ hook
  match value {
    Value::Instance(_) => {
      let hook = match
        get_attr_from_value(value, "__dir__", globals, builtins, io) {
        Ok(v) => v
        Err(_err) => Value::None
      }
      match hook {
        Value::None => ()
        _ => {
          let result = match
            call_callable_with_env(hook, [], [], globals, builtins, io) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          match result {
            Value::List(items) =>
              return Ok(Value::List(maybe_sort_str_list(items)))
            _ =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "__dir__ must return a list".to_string(),
                ),
              )
          }
        }
      }
    }
    // For type objects, CPython uses the metaclass' `__dir__` (type.__dir__),
    // not the class' own `__dir__` attribute meant for instances.
    _ => ()
  }

  // Default listing: include inherited names, sort, dedup.
  let names : Array[String] = []
  fn push_name(names : Array[String], name : String) -> Unit {
    if name == "__class__" ||
      name == "hashvalue" ||
      name.has_prefix("__mpython_") ||
      name == "__name__" ||
      name == "__qualname__" {
      // Hide internal or compatibility-only attributes from `dir()`.
      return
    }
    for existing in names {
      if existing == name {
        return
      }
    }
    names.push(name)
  }

  match value {
    Value::Dict(pairs) =>
      for pair in pairs {
        match pair.0 {
          Value::Str(name) => push_name(names, name)
          _ => ()
        }
      }
    Value::Instance(inst) => {
      for pair in inst.dict {
        push_name(names, pair.0)
      }
      let mro = match class_mro(inst.class) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      for cls in mro {
        for pair in cls.dict {
          if pair.0 != "__getattribute__" &&
            pair.0 != "__setattr__" &&
            pair.0 != "__delattr__" {
            push_name(names, pair.0)
          }
        }
      }
    }
    Value::Class(klass) => {
      let mro = match class_mro(klass) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      for cls in mro {
        for pair in cls.dict {
          if pair.0 != "__getattribute__" &&
            pair.0 != "__setattr__" &&
            pair.0 != "__delattr__" {
            push_name(names, pair.0)
          }
        }
      }
    }
    _ => ()
  }
  lex_sort_strings_in_place(names)
  let out : Array[Value] = []
  for name in names {
    out.push(Value::Str(name))
  }
  Ok(Value::List(out))
}

///|
fn builtin_getattr(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("getattr", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let _ = io
  let _ = locals
  let _ = globals
  let _ = builtins
  if positional.length() != 2 && positional.length() != 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "getattr() takes 2 or 3 arguments".to_string(),
      ),
    )
  }
  let obj = positional[0]
  let name = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "attribute name must be str".to_string(),
        ),
      )
  }
  match get_attr_from_value(obj, name, globals, builtins, io) {
    Ok(value) => Ok(value)
    Err(err) =>
      if positional.length() == 3 {
        match err.kind {
          RuntimeErrorKind::Attribute => Ok(positional[2])
          _ => Err(err)
        }
      } else {
        Err(err)
      }
  }
}

///|
fn builtin_hasattr(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("hasattr", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let _ = io
  let _ = locals
  let _ = globals
  let _ = builtins
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "hasattr() takes exactly two arguments".to_string(),
      ),
    )
  }
  let obj = positional[0]
  let name = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "attribute name must be str".to_string(),
        ),
      )
  }
  match get_attr_from_value(obj, name, globals, builtins, io) {
    Ok(_) => Ok(Value::Bool(true))
    Err(_err) => Ok(Value::Bool(false))
  }
}

///|
fn builtin_setattr(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("setattr", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let _ = io
  let _ = locals
  let _ = globals
  let _ = builtins
  if positional.length() != 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "setattr() takes exactly three arguments".to_string(),
      ),
    )
  }
  let target = positional[0]
  let name = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "attribute name must be str".to_string(),
        ),
      )
  }
  let value = positional[2]
  let _ = match set_attr_on_value(target, name, value, globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::None)
}

///|
fn builtin_super(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("super", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let _ = io

  // Support:
  // - super(type, obj)
  // - super() best-effort: uses closure __class__ and locals "self" / "cls"
  let mut thisclass_value : Value = Value::None
  let mut self_value : Value = Value::None
  if positional.length() == 0 {
    fn read_local_name(
      name : String,
      locals : Array[(String, Value)],
    ) -> Result[Value?, RuntimeError] {
      match get_local_value(locals, name) {
        Some(v) =>
          if is_cell_value(v) {
            match cell_get_value(v) {
              Some(value) => Ok(Some(value))
              None => Ok(None)
            }
          } else {
            Ok(Some(v))
          }
        None => Ok(None)
      }
    }

    let cls_opt = match
      lookup_name_value("__class__", locals, globals, builtins) {
      Ok(v) => Some(v)
      Err(err) =>
        if err.exc_type == "NameError" {
          None
        } else {
          return Err(err)
        }
    }
    if cls_opt is None {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "RuntimeError: super(): no __class__ in scope".to_string(),
        ),
      )
    }
    thisclass_value = cls_opt.unwrap()
    let self_opt = match read_local_name("self", locals) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    let cls_opt2 = match read_local_name("cls", locals) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    let mcls_opt = match read_local_name("mcls", locals) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    let type_opt = match read_local_name("type", locals) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    if self_opt is Some(v) {
      self_value = v
    } else if cls_opt2 is Some(v) {
      self_value = v
    } else if mcls_opt is Some(v) {
      self_value = v
    } else if type_opt is Some(v) {
      self_value = v
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "RuntimeError: super(): no self/cls in scope".to_string(),
        ),
      )
    }
  } else if positional.length() == 2 {
    thisclass_value = positional[0]
    self_value = positional[1]
  } else {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "super() takes 0 or 2 arguments".to_string(),
      ),
    )
  }
  let thisclass = match thisclass_value {
    Value::Class(k) => k
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "super() arg 1 must be a type".to_string(),
        ),
      )
  }

  // Determine the dynamic class to do MRO search on
  let self_class = match self_value {
    Value::Instance(inst) => inst.class
    Value::Class(k) => k
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "super() arg 2 must be an instance or type".to_string(),
        ),
      )
  }

  // Ensure thisclass is in self_class mro
  let mro = match class_mro(self_class) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let mut found = false
  for c in mro {
    if class_identity_equal(c, thisclass) {
      found = true
      break
    }
  }
  if !found {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "super() arguments are inconsistent".to_string(),
      ),
    )
  }
  let dict : Array[(String, Value)] = []
  dict.push(("__thisclass__", Value::Class(thisclass)))
  dict.push(("__self__", self_value))
  dict.push(("__self_class__", Value::Class(self_class)))
  Ok(
    Value::Instance(InstanceValue::{
      class: ClassValue::{ name: "super", bases: [], dict: [], },
      dict,
    }),
  )
}

///|
fn builtin_delattr(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("delattr", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let _ = locals
  let _ = builtins
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "delattr() takes exactly two arguments".to_string(),
      ),
    )
  }
  let target = positional[0]
  let name = match positional[1] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "attribute name must be str".to_string(),
        ),
      )
  }
  let _ = match delete_attr_on_value(target, name, globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::None)
}