///|
/// Function-definition semantics shared by the AST evaluator and the bytecode VM.

///|
fn make_function_from_template_with_env(
  template : FunctionValue,
  defaults : Array[Value],
  decorators : Array[Value],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  let (global_names, nonlocal_names) = collect_scope_decl_names(template.body)
  let _ = match
    check_scope_decl_conflicts(template.params, global_names, nonlocal_names) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let extra_cells : Array[(String, Value)] = []
  for nonlocal_name in nonlocal_names {
    match get_local_value(locals, nonlocal_name) {
      Some(v) => ensure_local_cell(locals, nonlocal_name, deref_cell_value(v))
      None =>
        match lookup_closure_cell(nonlocal_name) {
          Some(cell) => extra_cells.push((nonlocal_name, cell))
          None =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "SyntaxError: no binding for nonlocal '" +
                nonlocal_name +
                "' found",
              ),
            )
        }
    }
  }
  let inherited = merge_cell_closures(current_closure_env(), extra_cells)
  let closure = merge_cell_closures(locals, inherited)
  let globals_marker = Value::Instance(InstanceValue::{
    class: ClassValue::{ name: "__mpython_globals__", bases: [], dict: [], },
    dict: globals,
  })
  closure.push(("$__mpython_globals__", globals_marker))
  let firstlineno = match current_span() {
    Some(span) => @bigint.BigInt::from_int(span.line)
    None => {
      let frames = snapshot_traceback()
      if frames.length() > 0 {
        @bigint.BigInt::from_int(frames[frames.length() - 1].line)
      } else {
        1N
      }
    }
  }
  closure.push((firstlineno_capture_name, Value::Int(firstlineno)))
  let func = FunctionValue::{
    name: template.name,
    params: template.params,
    defaults,
    body: template.body,
    is_generator: template.is_generator,
    is_async: template.is_async,
    closure,
  }
  let mut defined : Value = Value::Function(func)
  for i = decorators.length(); i > 0; i = i - 1 {
    let decorator_value = decorators[i - 1]
    defined = match decorator_value {
      Value::Function(dec_func) =>
        if dec_func.body.length() == 0 {
          match
            eval_builtin_call(
              dec_func.name,
              [defined],
              [],
              locals,
              globals,
              builtins,
              io,
            ) {
            Ok(Some(value)) => value
            Ok(None) =>
              match
                call_callable_with_env(
                  decorator_value,
                  [defined],
                  [],
                  globals,
                  builtins,
                  io,
                ) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
            Err(err) => return Err(err)
          }
        } else {
          match
            call_callable_with_env(
              decorator_value,
              [defined],
              [],
              globals,
              builtins,
              io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
        }
      _ =>
        match
          call_callable_with_env(
            decorator_value,
            [defined],
            [],
            globals,
            builtins,
            io,
          ) {
          Ok(value) => value
          Err(err) => return Err(err)
        }
    }
  }
  Ok(defined)
}