///|
fn WgslIrLowerer::lower_function_math_call(
  self : WgslIrLowerer,
  math_function : MathFunction,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let expression_mark = function.expressions.items.length()
  let local_constant_snapshot = wgsl_ir_snapshot_local_constant_values(
    scope.local_constants,
  )
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() > 0 else {
    raise UnsupportedExpression("math call requires at least one argument")
  }
  if wgsl_ir_math_call_predeclares_function_argument_values(arguments, scope) {
    for argument in arguments {
      self.predeclare_function_argument_values_in_source_order(
        argument, function, scope,
      )
    }
  }
  match
    self.lower_function_math_call_with_later_splat_constructors_before_first_direct(
      math_function, arguments, function, scope,
    ) {
    Some(handle) => return handle
    None => ()
  }
  let deferred_constructors = self.prelower_homogeneous_math_constructor_arguments(
    math_function, arguments, function, scope,
  )
  if wgsl_ir_math_call_has_later_function_argument_values(arguments, scope) &&
    !deferred_constructors.contains(0) {
    match
      self.prelower_math_splat_constructor_argument_from_later_context(
        arguments, function, scope,
      ) {
      Some(value) => deferred_constructors.set(0, value)
      None => ()
    }
  }
  if self.math_call_predeclares_later_function_arguments_after_first_consts(
      math_function, arguments, deferred_constructors, function, scope,
    ) {
    self.predeclare_inline_constant_references_in_source_order(
      arguments[0],
      function,
      scope,
    )
    for index in 1.. handles.push(handle)
        None =>
          handles.push(
            self.lower_function_math_argument_at(
              index, arguments, deferred_constructors, function, scope,
            ),
          )
      }
    }
  } else {
    for index in 0.. Some(value)
    [] => None
  }
  let c = match rest {
    [_, value, ..] => Some(value)
    _ => None
  }
  let d = match rest {
    [_, _, value, ..] => Some(value)
    _ => None
  }
  let handle = function.expressions.append(Math(math_function, a, b, c, d))
  match self.fold_function_constant_float_expression(function, handle) {
    Some(literal) => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      function.expressions.append_with_origin(
        Literal(literal),
        FoldedLocalConstant,
      )
    }
    None => handle
  }
}

///|
fn WgslIrLowerer::predeclare_math_later_direct_argument_after_first_constructor_component(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  guard arguments.length() >= 3 else { return }
  guard arguments[0].kind() is Call(callee, constructor_arguments) else {
    return
  }
  guard self.expression_callee_is_constructor_call_value(callee) else { return }
  guard wgsl_ir_flatten_expression_arguments(constructor_arguments)
    is [component] else {
    return
  }
  guard wgsl_ir_expr_node_is_function_argument_value(component, scope) else {
    return
  }
  let mut direct_index = -1
  for index in 2..= 0 else { return }
  ignore(self.lower_function_expression_node(component, function, scope))
  self.predeclare_function_argument_value(
    arguments[direct_index],
    function,
    scope,
  )
}

///|
fn WgslIrLowerer::lower_function_math_call_with_later_splat_constructors_before_first_direct(
  self : WgslIrLowerer,
  math_function : MathFunction,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle? raise WgslIrLowerError {
  guard math_function == Clamp else { return None }
  guard wgsl_ir_math_function_has_homogeneous_arguments(math_function) else {
    return None
  }
  guard arguments.length() >= 2 else { return None }
  guard wgsl_ir_expr_node_is_function_argument_value(arguments[0], scope) else {
    return None
  }
  guard self.prelower_math_splat_constructor_argument(
      arguments[1],
      function,
      scope,
    )
    is Some(first_later) else {
    return None
  }
  let second = self.lower_function_constructor_expression_from_handles(
    first_later.ty,
    [first_later.component],
    function,
  )
  let deferred : Map[Int32, WgslIrDeferredConstructorArgument] = Map([])
  for index in 2.. deferred.set(index, value)
      None => return None
    }
  }
  let first = self.lower_function_math_argument_at(
    0, arguments, deferred, function, scope,
  )
  let handles : Array[Handle] = [first, second]
  for index in 2.. Some(value)
    [] => None
  }
  let c = match rest {
    [_, value, ..] => Some(value)
    _ => None
  }
  let d = match rest {
    [_, _, value, ..] => Some(value)
    _ => None
  }
  Some(function.expressions.append(Math(math_function, a, b, c, d)))
}

///|
fn wgsl_ir_math_call_has_later_function_argument_values(
  arguments : Array[@ast.WgslExprNode],
  scope : WgslIrFunctionScope,
) -> Bool {
  guard arguments.length() > 1 else { return false }
  for index in 1.. WgslIrDeferredConstructorArgument? raise WgslIrLowerError {
  guard arguments.length() > 1 else { return None }
  guard arguments[0].kind() is Call(_, constructor_arguments) else {
    return None
  }
  guard wgsl_ir_flatten_expression_arguments(constructor_arguments)
    is [component] else {
    return None
  }
  guard wgsl_ir_expr_node_is_foldable_float_arena_candidate(component) else {
    return None
  }
  guard self.math_call_later_function_argument_context_type(
      arguments, function, scope,
    )
    is Some(expected_ty) else {
    return None
  }
  self.prelower_math_splat_constructor_argument_with_expected_type(
    arguments[0],
    expected_ty,
    function,
    scope,
  )
}

///|
fn WgslIrLowerer::math_call_later_function_argument_context_type(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle? raise WgslIrLowerError {
  for index in 1.. WgslIrDeferredConstructorArgument? raise WgslIrLowerError {
  guard argument.kind() is Call(callee, constructor_arguments) else {
    return None
  }
  guard wgsl_ir_flatten_expression_arguments(constructor_arguments)
    is [component] else {
    return None
  }
  guard wgsl_ir_expr_node_is_foldable_float_arena_candidate(component) else {
    return None
  }
  guard self.vector_size_from_type(expected_ty) is Some(expected_size) else {
    return None
  }
  let ty = match self.lower_templated_callee_type(callee) {
    Some(ty) => ty
    None =>
      match callee.kind() {
        Identifier(name) =>
          match wgsl_ir_vector_size_from_constructor_name(name.name()) {
            Some(size) if size == expected_size => expected_ty
            _ => return None
          }
        _ => return None
      }
  }
  guard self.constructor_component_expected_type(ty) is Some(component_ty) else {
    return None
  }
  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(
    component,
    function,
    scope,
    Some(component_ty),
  )
  match self.fold_function_constant_float_expression(function, lowered) {
    Some(literal) => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      let component = function.expressions.append_with_origin(
        Literal(literal),
        FoldedLocalConstant,
      )
      Some({ ty, component, })
    }
    None => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      None
    }
  }
}

///|
fn WgslIrLowerer::math_call_predeclares_later_function_arguments_after_first_consts(
  self : WgslIrLowerer,
  math_function : MathFunction,
  arguments : Array[@ast.WgslExprNode],
  deferred_constructors : Map[Int32, WgslIrDeferredConstructorArgument],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  guard wgsl_ir_math_function_has_homogeneous_arguments(math_function) else {
    return false
  }
  guard arguments.length() > 1 else { return false }
  let first_has_const_leaf = deferred_constructors.contains(0) ||
    self.expression_node_contains_inline_constant_reference(arguments[0], scope)
  guard first_has_const_leaf else { return false }
  for index in 1.. Bool {
  wgsl_ir_expr_node_is_direct_access_chain(argument) &&
  self.expression_node_contains_writer_reference_value(
    argument, function, scope,
  )
}

///|
fn WgslIrLowerer::math_call_predeclares_later_direct_arguments_before_reference_first(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  guard arguments.length() > 1 else { return false }
  guard wgsl_ir_expr_node_is_direct_access_chain(arguments[0]) else {
    return false
  }
  guard self.expression_node_contains_writer_reference_value(
    arguments[0],
    function,
    scope,
  ) else {
    return false
  }
  for index in 1.. Bool {
  arguments.length() > 1 &&
  arguments.any(fn(argument) {
    wgsl_ir_expr_node_contains_function_argument_math_call(argument, scope)
  })
}

///|
fn WgslIrLowerer::predeclare_inline_constant_references_in_source_order(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  match node.kind() {
    Identifier(name) =>
      if self.expression_node_is_inline_constant_reference(node, scope) {
        ignore(self.lower_identifier_expression(name.name(), function, scope))
      }
    Group(inner) | Unary(_, inner) | PostfixUpdate(_, inner) =>
      self.predeclare_inline_constant_references_in_source_order(
        inner, function, scope,
      )
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) => {
      self.predeclare_inline_constant_references_in_source_order(
        left, function, scope,
      )
      self.predeclare_inline_constant_references_in_source_order(
        right, function, scope,
      )
    }
    Member(base, _) | PathAccess(base, _) | Template(base, _) =>
      self.predeclare_inline_constant_references_in_source_order(
        base, function, scope,
      )
    Call(_, arguments) | Sequence(arguments) =>
      for argument in arguments {
        self.predeclare_inline_constant_references_in_source_order(
          argument, function, scope,
        )
      }
    Literal(_) | ShaderDefTemplate(_) => ()
  }
}

///|
fn WgslIrLowerer::expression_node_contains_inline_constant_reference(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  scope : WgslIrFunctionScope,
) -> Bool {
  match node.kind() {
    Identifier(_) =>
      self.expression_node_is_inline_constant_reference(node, scope)
    Group(inner) | Unary(_, inner) | PostfixUpdate(_, inner) =>
      self.expression_node_contains_inline_constant_reference(inner, scope)
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) =>
      self.expression_node_contains_inline_constant_reference(left, scope) ||
      self.expression_node_contains_inline_constant_reference(right, scope)
    Member(base, _) | PathAccess(base, _) | Template(base, _) =>
      self.expression_node_contains_inline_constant_reference(base, scope)
    Call(_, arguments) | Sequence(arguments) =>
      arguments.any(fn(argument) {
        self.expression_node_contains_inline_constant_reference(argument, scope)
      })
    Literal(_) | ShaderDefTemplate(_) => false
  }
}

///|
fn WgslIrLowerer::predeclare_function_argument_values_in_source_order(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  match node.kind() {
    Identifier(_) =>
      self.predeclare_function_argument_value(node, function, scope)
    Group(inner) | Unary(_, inner) | PostfixUpdate(_, inner) =>
      self.predeclare_function_argument_values_in_source_order(
        inner, function, scope,
      )
    Binary(_, left, right) | Assignment(_, left, right) | Index(left, right) => {
      self.predeclare_function_argument_values_in_source_order(
        left, function, scope,
      )
      self.predeclare_function_argument_values_in_source_order(
        right, function, scope,
      )
    }
    Member(base, _) | PathAccess(base, _) | Template(base, _) =>
      self.predeclare_function_argument_values_in_source_order(
        base, function, scope,
      )
    Call(_, arguments) | Sequence(arguments) =>
      for argument in arguments {
        self.predeclare_function_argument_values_in_source_order(
          argument, function, scope,
        )
      }
    Literal(_) | ShaderDefTemplate(_) => ()
  }
}

///|
fn WgslIrLowerer::lower_function_math_argument_at(
  self : WgslIrLowerer,
  index : Int32,
  arguments : Array[@ast.WgslExprNode],
  deferred_constructors : Map[Int32, WgslIrDeferredConstructorArgument],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let argument = arguments[index]
  let lowered = match deferred_constructors.get(index) {
    Some(deferred) =>
      self.lower_function_constructor_expression_from_handles(
        deferred.ty,
        [deferred.component],
        function,
      )
    None => self.lower_function_expression_node(argument, function, scope)
  }
  self.apply_function_load_rule(function, lowered)
}

///|
fn WgslIrLowerer::lower_function_foldable_const_math_argument_at(
  self : WgslIrLowerer,
  index : Int32,
  arguments : Array[@ast.WgslExprNode],
  deferred_constructors : Map[Int32, WgslIrDeferredConstructorArgument],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  guard self.expression_node_is_foldable_math_const_like(
    arguments[index],
    scope,
  ) else {
    return self.lower_function_math_argument_at(
      index, arguments, deferred_constructors, function, scope,
    )
  }
  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_math_argument_at(
    index, arguments, deferred_constructors, function, scope,
  )
  match self.fold_function_constant_literal_expression(function, lowered) {
    Some(literal) => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      function.expressions.append_with_origin(
        Literal(literal),
        FoldedLocalConstant,
      )
    }
    None => lowered
  }
}

///|
fn WgslIrLowerer::math_call_lowers_second_argument_before_first(
  self : WgslIrLowerer,
  math_function : MathFunction,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  guard wgsl_ir_math_function_has_homogeneous_arguments(math_function) else {
    return false
  }
  guard arguments.length() >= 2 else { return false }
  (
    wgsl_ir_expr_node_is_literal_like(arguments[0]) &&
    self.expression_node_contains_writer_reference_value(
      arguments[1],
      function,
      scope,
    )
  ) ||
  (
    wgsl_ir_expr_node_is_foldable_float_splat_constructor(arguments[0]) &&
    self.expression_node_contains_writer_reference_value(
      arguments[1],
      function,
      scope,
    )
  ) ||
  (
    self.expression_node_is_foldable_math_const_like(arguments[0], scope) &&
    self.expression_node_contains_user_function_call(arguments[1])
  )
}

///|
fn wgsl_ir_expr_node_is_foldable_float_splat_constructor(
  node : @ast.WgslExprNode,
) -> Bool {
  match node.kind() {
    Group(inner) => wgsl_ir_expr_node_is_foldable_float_splat_constructor(inner)
    Call(callee, arguments) =>
      wgsl_ir_callee_is_vector_constructor(callee) &&
      wgsl_ir_flatten_expression_arguments(arguments) is [component] &&
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(component)
    _ => false
  }
}

///|
fn wgsl_ir_callee_is_vector_constructor(callee : @ast.WgslExprNode) -> Bool {
  match callee.kind() {
    Identifier(name) =>
      wgsl_ir_vector_size_from_constructor_name(name.name()) != None
    Template(base, _) => wgsl_ir_callee_is_vector_constructor(base)
    Group(inner) => wgsl_ir_callee_is_vector_constructor(inner)
    _ => false
  }
}

///|
fn WgslIrLowerer::prelower_math_splat_constructor_argument(
  self : WgslIrLowerer,
  argument : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> WgslIrDeferredConstructorArgument? raise WgslIrLowerError {
  guard argument.kind() is Call(callee, constructor_arguments) else {
    return None
  }
  guard wgsl_ir_flatten_expression_arguments(constructor_arguments)
    is [component] else {
    return None
  }
  guard self.expression_node_is_writer_deferred_const_like(component, scope) else {
    return None
  }
  guard self.lower_math_splat_constructor_callee_type(
      callee, constructor_arguments,
    )
    is Some(ty) else {
    return None
  }
  guard self.vector_size_from_type(ty) is Some(_) else { return None }
  let expected_ty = self.constructor_component_expected_type(ty)
  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(
    component, function, scope, expected_ty,
  )
  match self.fold_function_constant_float_expression(function, lowered) {
    Some(literal) => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      let component = function.expressions.append_with_origin(
        Literal(literal),
        FoldedLocalConstant,
      )
      Some({ ty, component, })
    }
    None => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      None
    }
  }
}

///|
fn WgslIrLowerer::lower_math_splat_constructor_callee_type(
  self : WgslIrLowerer,
  callee : @ast.WgslExprNode,
  constructor_arguments : Array[@ast.WgslExprNode],
) -> Handle? raise WgslIrLowerError {
  match self.lower_templated_callee_type(callee) {
    Some(ty) => return Some(ty)
    None => ()
  }
  match callee.kind() {
    Identifier(name) => {
      let constructor_name = name.name()
      guard wgsl_ir_vector_size_from_constructor_name(constructor_name)
        is Some(_) else {
        return None
      }
      Some(
        self.lower_untemplated_constructor_type_from_ast(
          constructor_name, constructor_arguments, constructor_name,
        ),
      )
    }
    _ => None
  }
}

///|
priv struct WgslIrDeferredConstructorArgument {
  ty : Handle
  component : Handle
}

///|
fn WgslIrLowerer::prelower_homogeneous_math_constructor_arguments(
  self : WgslIrLowerer,
  math_function : MathFunction,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Map[Int32, WgslIrDeferredConstructorArgument] raise WgslIrLowerError {
  let deferred : Map[Int32, WgslIrDeferredConstructorArgument] = Map([])
  guard wgsl_ir_math_function_has_homogeneous_arguments(math_function) else {
    return deferred
  }
  for index in 0.. deferred.set(index, value)
      None => ()
    }
  }
  deferred
}

///|
fn WgslIrLowerer::prelower_foldable_vector_constructor_argument(
  self : WgslIrLowerer,
  argument : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> WgslIrDeferredConstructorArgument? raise WgslIrLowerError {
  guard argument.kind() is Call(callee, constructor_arguments) else {
    return None
  }
  guard wgsl_ir_flatten_expression_arguments(constructor_arguments)
    is [component] else {
    return None
  }
  guard wgsl_ir_expr_node_is_foldable_float_arena_candidate(component) else {
    return None
  }
  guard self.lower_foldable_vector_constructor_callee_type(
      callee, constructor_arguments, component,
    )
    is Some(ty) else {
    return None
  }
  guard self.vector_size_from_type(ty) is Some(_) else { return None }
  let expected_ty = self.constructor_component_expected_type(ty)
  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(
    component, function, scope, expected_ty,
  )
  match self.fold_function_constant_float_expression(function, lowered) {
    Some(literal) => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      let component = function.expressions.append_with_origin(
        Literal(literal),
        FoldedLocalConstant,
      )
      Some({ ty, component, })
    }
    None => {
      wgsl_ir_rewind_function_expressions_and_local_constant_values(
        function, expression_mark, scope, local_constant_snapshot,
      )
      None
    }
  }
}

///|
fn WgslIrLowerer::lower_foldable_vector_constructor_callee_type(
  self : WgslIrLowerer,
  callee : @ast.WgslExprNode,
  constructor_arguments : Array[@ast.WgslExprNode],
  component : @ast.WgslExprNode,
) -> Handle? raise WgslIrLowerError {
  match self.lower_templated_callee_type(callee) {
    Some(ty) => return Some(ty)
    None => ()
  }
  match callee.kind() {
    Identifier(name) => {
      let constructor_name = name.name()
      guard wgsl_ir_vector_size_from_constructor_name(constructor_name)
        is Some(_) else {
        return None
      }
      guard wgsl_ir_expr_node_requires_float_arena_fold(component) else {
        return None
      }
      Some(
        self.lower_untemplated_constructor_type_from_ast(
          constructor_name, constructor_arguments, constructor_name,
        ),
      )
    }
    _ => None
  }
}

///|
fn wgsl_ir_expr_node_requires_float_arena_fold(
  node : @ast.WgslExprNode,
) -> Bool {
  match node.kind() {
    Group(inner) => wgsl_ir_expr_node_requires_float_arena_fold(inner)
    Sequence([single]) => wgsl_ir_expr_node_requires_float_arena_fold(single)
    Unary(_, inner) =>
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(inner)
    Binary(_, left, right) =>
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(left) ||
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(right)
    _ => false
  }
}

///|
fn wgsl_ir_expr_node_is_foldable_float_arena_candidate(
  node : @ast.WgslExprNode,
) -> Bool {
  match node.kind() {
    Literal(_) => true
    Group(inner) => wgsl_ir_expr_node_is_foldable_float_arena_candidate(inner)
    Sequence([single]) =>
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(single)
    Unary(op, inner) =>
      op == Subtract &&
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(inner)
    Binary(_, left, right) =>
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(left) &&
      wgsl_ir_expr_node_is_foldable_float_arena_candidate(right)
    _ => false
  }
}

///|
fn WgslIrLowerer::contextualize_homogeneous_math_arguments(
  self : WgslIrLowerer,
  math_function : MathFunction,
  function : Function,
  handles : Array[Handle],
) -> Unit raise WgslIrLowerError {
  guard wgsl_ir_math_function_has_homogeneous_arguments(math_function) else {
    return
  }
  guard self.concrete_math_argument_type(function, handles) is Some(expected_ty) else {
    return
  }
  for handle in handles {
    self.contextualize_function_literal_expression(
      function, handle, expected_ty,
    )
  }
}

///|
fn wgsl_ir_math_function_has_homogeneous_arguments(
  math_function : MathFunction,
) -> Bool {
  match math_function {
    Min
    | Max
    | Clamp
    | Step
    | SmoothStep
    | Mix
    | Dot
    | Distance
    | Reflect
    | FaceForward
    | Refract
    | Fma
    | Pow
    | Atan2 => true
    _ => false
  }
}

///|
fn WgslIrLowerer::concrete_math_argument_type(
  self : WgslIrLowerer,
  function : Function,
  handles : Array[Handle],
) -> Handle? {
  for handle in handles {
    match self.function_expression_type(function, handle) {
      Some(ty) =>
        if self.type_is_concrete_numeric_literal_context(ty) {
          return Some(ty)
        }
      None => ()
    }
  }
  None
}

///|
fn WgslIrLowerer::type_is_concrete_numeric_literal_context(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.scalar_for_literal_context_type(ty) {
    Some(scalar) =>
      match scalar.kind {
        Sint | Uint | Float => true
        _ => false
      }
    None => false
  }
}