///|
fn WgslIrLowerer::lower_function_expression_arguments(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Array[Handle] raise WgslIrLowerError {
  self.lower_function_expression_arguments_with_expected(
    arguments,
    function,
    scope,
    None,
  )
}

///|
fn WgslIrLowerer::lower_function_expression_arguments_with_expected(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Array[Handle] raise WgslIrLowerError {
  let handles : Array[Handle] = []
  for argument in wgsl_ir_flatten_expression_arguments(arguments) {
    let lowered = self.lower_function_expression_node_with_expected(
      argument, function, scope, expected_ty,
    )
    handles.push(self.apply_function_load_rule(function, lowered))
  }
  handles
}

///|
fn WgslIrLowerer::lower_function_constructor_arguments(
  self : WgslIrLowerer,
  ty : Handle,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Array[Handle] raise WgslIrLowerError {
  let flattened = wgsl_ir_flatten_expression_arguments(arguments)
  let expected_ty = self.constructor_argument_expected_type(
    ty,
    flattened.length(),
  )
  self.lower_function_flattened_constructor_arguments(
    flattened, function, scope, expected_ty,
  )
}

///|
fn WgslIrLowerer::lower_function_constructor_arguments_with_expected(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Array[Handle] raise WgslIrLowerError {
  self.lower_function_flattened_constructor_arguments(
    wgsl_ir_flatten_expression_arguments(arguments),
    function,
    scope,
    expected_ty,
  )
}

///|
fn WgslIrLowerer::lower_function_flattened_constructor_arguments(
  self : WgslIrLowerer,
  flattened : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Array[Handle] raise WgslIrLowerError {
  let scheduler = self.expression_arena_scheduler(function, scope)
  match
    self.lower_function_constructor_arguments_with_later_local_root_after_first_global_root(
      flattened, function, scope, expected_ty,
    ) {
    Some(handles) => return handles
    None => ()
  }
  if scheduler.constructor_arguments_delay_literals(flattened) {
    return self.lower_function_constructor_arguments_with_delayed_literals(
      flattened, function, scope, expected_ty,
    )
  }
  let handles : Array[Handle] = []
  for argument in flattened {
    let lowered = self.lower_function_constructor_argument(
      argument, function, scope, expected_ty,
    )
    handles.push(self.apply_function_load_rule(function, lowered))
  }
  handles
}

///|
fn WgslIrLowerer::lower_function_constructor_arguments_with_later_local_root_after_first_global_root(
  self : WgslIrLowerer,
  flattened : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Array[Handle]? raise WgslIrLowerError {
  guard flattened.length() >= 2 else { return None }
  let scheduler = self.expression_arena_scheduler(function, scope)
  guard !scheduler.constructor_argument_has_ordered_effect(flattened[0]) else {
    return None
  }
  guard self.first_global_static_member_root_end(flattened[0]) is Some(root_end) else {
    return None
  }
  guard self.expression_node_contains_mutable_local_root_after(
    flattened[0],
    root_end,
    function,
    scope,
  ) else {
    return None
  }
  guard self.predeclare_first_global_static_member_root(
    flattened[0],
    function,
    scope,
  ) else {
    return None
  }
  guard self.predeclare_first_mutable_local_root_after(
    flattened[0],
    root_end,
    function,
    scope,
  ) else {
    return None
  }
  let handles : Array[Handle] = []
  for index in 0.. Int32? {
  match node.kind() {
    Member(_, _) =>
      match wgsl_ir_static_member_access_chain(node) {
        Some((root, members)) =>
          if members.length() > 0 &&
            self.expression_node_is_global_reference_value(root) {
            Some(node.end())
          } else {
            None
          }
        None => None
      }
    Group(inner) | Unary(_, inner) | PostfixUpdate(_, inner) =>
      self.first_global_static_member_root_end(inner)
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) =>
      match self.first_global_static_member_root_end(left) {
        Some(end_) => Some(end_)
        None => self.first_global_static_member_root_end(right)
      }
    Call(callee, arguments) => {
      let arguments = wgsl_ir_call_arguments(arguments)
      match self.first_global_static_member_root_end(callee) {
        Some(end_) => Some(end_)
        None => {
          for argument in arguments {
            match self.first_global_static_member_root_end(argument) {
              Some(end_) => return Some(end_)
              None => ()
            }
          }
          None
        }
      }
    }
    Sequence(arguments) => {
      for argument in arguments {
        match self.first_global_static_member_root_end(argument) {
          Some(end_) => return Some(end_)
          None => ()
        }
      }
      None
    }
    Template(base, _) | PathAccess(base, _) =>
      self.first_global_static_member_root_end(base)
    Identifier(_) | Literal(_) | ShaderDefTemplate(_) => None
  }
}

///|
fn WgslIrLowerer::predeclare_first_global_static_member_root(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool raise WgslIrLowerError {
  match node.kind() {
    Member(_, _) =>
      match wgsl_ir_static_member_access_chain(node) {
        Some((root, members)) =>
          if members.length() > 0 &&
            self.predeclare_global_identifier_root(root, function, scope) {
            true
          } else {
            false
          }
        None => false
      }
    Group(inner) | Unary(_, inner) | PostfixUpdate(_, inner) =>
      self.predeclare_first_global_static_member_root(inner, function, scope)
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) =>
      if self.predeclare_first_global_static_member_root(left, function, scope) {
        true
      } else {
        self.predeclare_first_global_static_member_root(right, function, scope)
      }
    Call(callee, arguments) =>
      if self.predeclare_first_global_static_member_root(
          callee, function, scope,
        ) {
        true
      } else {
        let arguments = wgsl_ir_call_arguments(arguments)
        let mut found = false
        for argument in arguments {
          if !found &&
            self.predeclare_first_global_static_member_root(
              argument, function, scope,
            ) {
            found = true
          }
        }
        found
      }
    Sequence(arguments) => {
      let mut found = false
      for argument in arguments {
        if !found &&
          self.predeclare_first_global_static_member_root(
            argument, function, scope,
          ) {
          found = true
        }
      }
      found
    }
    Template(base, _) | PathAccess(base, _) =>
      self.predeclare_first_global_static_member_root(base, function, scope)
    Identifier(_) | Literal(_) | ShaderDefTemplate(_) => false
  }
}

///|
fn WgslIrLowerer::predeclare_global_identifier_root(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  match node.kind() {
    Group(inner) =>
      self.predeclare_global_identifier_root(inner, function, scope)
    Identifier(name) =>
      match self.global_names.get(name.name()) {
        Some(handle) => {
          ignore(
            scope.predeclare_global_pointer_expression(
              name.name(),
              handle,
              function,
            ),
          )
          true
        }
        None => false
      }
    _ => false
  }
}

///|
fn WgslIrLowerer::expression_node_contains_mutable_local_root_after(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  after_offset : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  match node.kind() {
    Identifier(name) =>
      if node.start() >= after_offset {
        match scope.locals.get(name.name()) {
          Some(handle) =>
            match function.local_variables.items.get(handle.index()) {
              Some(local_var) => local_var.kind == Var
              None => false
            }
          None => false
        }
      } else {
        false
      }
    Group(inner) =>
      self.expression_node_contains_mutable_local_root_after(
        inner, after_offset, function, scope,
      )
    Member(base, _) | PathAccess(base, _) | Template(base, _) =>
      self.expression_node_contains_mutable_local_root_after(
        base, after_offset, function, scope,
      )
    Unary(_, expr) | PostfixUpdate(_, expr) =>
      self.expression_node_contains_mutable_local_root_after(
        expr, after_offset, function, scope,
      )
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) =>
      self.expression_node_contains_mutable_local_root_after(
        left, after_offset, function, scope,
      ) ||
      self.expression_node_contains_mutable_local_root_after(
        right, after_offset, function, scope,
      )
    Call(callee, arguments) =>
      if self.expression_node_contains_mutable_local_root_after(
          callee, after_offset, function, scope,
        ) {
        true
      } else {
        let call_args = wgsl_ir_call_arguments(arguments)
        call_args.any(fn(argument) {
          self.expression_node_contains_mutable_local_root_after(
            argument, after_offset, function, scope,
          )
        })
      }
    Sequence(arguments) =>
      arguments.any(fn(argument) {
        self.expression_node_contains_mutable_local_root_after(
          argument, after_offset, function, scope,
        )
      })
    Literal(_) | ShaderDefTemplate(_) => false
  }
}

///|
fn WgslIrLowerer::predeclare_first_mutable_local_root_after(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  after_offset : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  match node.kind() {
    Identifier(name) =>
      if node.start() >= after_offset {
        match scope.locals.get(name.name()) {
          Some(handle) =>
            match function.local_variables.items.get(handle.index()) {
              Some(local_var) =>
                if local_var.kind == Var {
                  ignore(
                    scope.predeclare_local_pointer_expression(
                      name.name(),
                      handle,
                      function,
                    ),
                  )
                  true
                } else {
                  false
                }
              None => false
            }
          None => false
        }
      } else {
        false
      }
    Group(inner) =>
      self.predeclare_first_mutable_local_root_after(
        inner, after_offset, function, scope,
      )
    Member(base, _) | PathAccess(base, _) | Template(base, _) =>
      self.predeclare_first_mutable_local_root_after(
        base, after_offset, function, scope,
      )
    Unary(_, expr) | PostfixUpdate(_, expr) =>
      self.predeclare_first_mutable_local_root_after(
        expr, after_offset, function, scope,
      )
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) =>
      if self.predeclare_first_mutable_local_root_after(
          left, after_offset, function, scope,
        ) {
        true
      } else {
        self.predeclare_first_mutable_local_root_after(
          right, after_offset, function, scope,
        )
      }
    Call(callee, arguments) =>
      if self.predeclare_first_mutable_local_root_after(
          callee, after_offset, function, scope,
        ) {
        true
      } else {
        let call_args = wgsl_ir_call_arguments(arguments)
        let mut found = false
        for argument in call_args {
          if !found &&
            self.predeclare_first_mutable_local_root_after(
              argument, after_offset, function, scope,
            ) {
            found = true
          }
        }
        found
      }
    Sequence(arguments) => {
      let mut found = false
      for argument in arguments {
        if !found &&
          self.predeclare_first_mutable_local_root_after(
            argument, after_offset, function, scope,
          ) {
          found = true
        }
      }
      found
    }
    Literal(_) | ShaderDefTemplate(_) => false
  }
}

///|
fn WgslIrLowerer::lower_function_constructor_arguments_with_delayed_literals(
  self : WgslIrLowerer,
  flattened : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Array[Handle] raise WgslIrLowerError {
  let scheduler = self.expression_arena_scheduler(function, scope)
  let lowered : Array[Handle?] = []
  for _ in 0.. handles.push(handle)
      None => raise UnsupportedExpression("unlowered constructor argument")
    }
  }
  handles
}

///|
fn WgslIrLowerer::lower_function_constructor_argument_value(
  self : WgslIrLowerer,
  argument : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Handle raise WgslIrLowerError {
  let lowered = self.lower_function_constructor_argument(
    argument, function, scope, expected_ty,
  )
  self.apply_function_load_rule(function, lowered)
}

///|
fn WgslIrLowerer::lower_function_constructor_argument(
  self : WgslIrLowerer,
  argument : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
  expected_ty : Handle?,
) -> Handle raise WgslIrLowerError {
  let expression_mark = function.expressions.items.length()
  let local_constant_snapshot = wgsl_ir_snapshot_local_constant_values(
    scope.local_constants,
  )
  let lowered = self.lower_function_expression_node_with_expected(
    argument, function, scope, expected_ty,
  )
  if wgsl_ir_expr_node_references_local_let_constant(argument, scope) {
    return lowered
  }
  guard self.fold_function_constant_float_expression(function, lowered)
    is Some(literal) else {
    return lowered
  }
  wgsl_ir_rewind_function_expressions_and_local_constant_values(
    function, expression_mark, scope, local_constant_snapshot,
  )
  function.expressions.append_with_origin(Literal(literal), FoldedLocalConstant)
}

///|
fn WgslIrLowerer::lower_function_constructor_expression_from_handles(
  self : WgslIrLowerer,
  ty : Handle,
  lowered_arguments : Array[Handle],
  function : Function,
) -> Handle {
  match
    self.lower_function_matrix_constructor_expression_from_handles(
      ty, lowered_arguments, function,
    ) {
    Some(handle) => return handle
    None => ()
  }
  match self.vector_size_from_type(ty) {
    Some(size) =>
      match lowered_arguments {
        [argument] =>
          match
            self.lower_function_single_vector_constructor_argument(
              ty, size, argument, function,
            ) {
            Some(handle) => return handle
            None => ()
          }
        _ => ()
      }
    None => ()
  }
  function.expressions.append(Compose(ty, lowered_arguments))
}

///|
fn WgslIrLowerer::lower_function_matrix_constructor_expression_from_handles(
  self : WgslIrLowerer,
  ty : Handle,
  lowered_arguments : Array[Handle],
  function : Function,
) -> Handle? {
  guard self.shader_module.types.get(ty) is Some(type_) else { return None }
  guard type_.inner is Matrix(columns, rows, scalar) else { return None }
  let columns_len = columns.width()
  let rows_len = rows.width()
  guard lowered_arguments.length() == columns_len * rows_len else {
    return None
  }
  let column_ty = self.vector_type_handle(rows, scalar)
  let column_handles : Array[Handle] = []
  for column in 0.. Handle? {
  guard self.shader_module.types.get(ty) is Some(target_type) else {
    return None
  }
  guard target_type.inner is Vector(_, target_scalar) else { return None }
  guard self.function_expression_type(function, argument) is Some(argument_ty) else {
    return None
  }
  if self.type_is_scalar(argument_ty) {
    return Some(function.expressions.append(Splat(size, argument)))
  }
  match self.shader_module.types.get(argument_ty) {
    Some(argument_type) =>
      match argument_type.inner {
        Vector(argument_size, _) =>
          if argument_size != size {
            None
          } else {
            Some(
              function.expressions.append(
                As(argument, target_scalar.kind, Some(target_scalar.width)),
              ),
            )
          }
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::lower_function_array_constructor_call(
  self : WgslIrLowerer,
  expected_ty : Handle?,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let lowered_arguments = self.lower_function_expression_arguments(
    arguments, function, scope,
  )
  let ty = match expected_ty {
    Some(ty) => ty
    None =>
      self.infer_array_constructor_type_from_handles(
        lowered_arguments, function, "array",
      )
  }
  guard self.array_element_type(ty) is Some(element_ty) else {
    raise UnsupportedType("array")
  }
  let components = match expected_ty {
    Some(_) =>
      self.lower_function_expression_arguments_with_expected(
        arguments,
        function,
        scope,
        Some(element_ty),
      )
    None => lowered_arguments
  }
  function.expressions.append(Compose(ty, components))
}

///|
fn WgslIrLowerer::lower_function_array_length_call(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments is [argument] else {
    raise UnsupportedExpression("arrayLength expects one argument")
  }
  let pointer = self.lower_atomic_pointer_argument(argument, function, scope)
  function.expressions.append(ArrayLength(pointer))
}

///|
fn WgslIrLowerer::lower_function_workgroup_uniform_load_call(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments is [argument] else {
    raise UnsupportedExpression("workgroupUniformLoad expects one argument")
  }
  let pointer = self.lower_atomic_pointer_argument(argument, function, scope)
  function.expressions.append(WorkGroupUniformLoad(pointer))
}