///|
fn WgslIrLowerer::member_index_for_expression(
  self : WgslIrLowerer,
  function : Function,
  expression_handle : Handle,
  member_name : String,
) -> Int32 raise WgslIrLowerError {
  match self.function_expression_type(function, expression_handle) {
    Some(ty_handle) =>
      match
        self.shader_module.types.get(self.deref_pointer_type_handle(ty_handle)) {
        Some(ty) =>
          match ty.inner {
            Struct(members, _) | PredeclaredStruct(members, _) =>
              match wgsl_ir_struct_member_index(members, member_name) {
                Some(index) => index
                None =>
                  raise UnsupportedExpression(
                    "unknown struct member \{member_name}",
                  )
              }
            _ => wgsl_ir_member_index(member_name)
          }
        None => wgsl_ir_member_index(member_name)
      }
    None => wgsl_ir_member_index(member_name)
  }
}

///|
fn WgslIrLowerer::member_index_for_global_expression(
  self : WgslIrLowerer,
  expression_handle : Handle,
  member_name : String,
) -> Int32 raise WgslIrLowerError {
  match self.global_expression_type(expression_handle) {
    Some(ty_handle) =>
      match
        self.shader_module.types.get(self.deref_pointer_type_handle(ty_handle)) {
        Some(ty) =>
          match ty.inner {
            Struct(members, _) | PredeclaredStruct(members, _) =>
              match wgsl_ir_struct_member_index(members, member_name) {
                Some(index) => index
                None =>
                  raise UnsupportedExpression(
                    "unknown struct member \{member_name}",
                  )
              }
            _ => wgsl_ir_member_index(member_name)
          }
        None => wgsl_ir_member_index(member_name)
      }
    None => wgsl_ir_member_index(member_name)
  }
}

///|
fn WgslIrLowerer::global_expression_type(
  self : WgslIrLowerer,
  handle : Handle,
) -> Handle? {
  match self.shader_module.global_expressions.items.get(handle.index()) {
    Some(expression) => self.global_expression_inner_type(expression)
    None => None
  }
}

///|
fn WgslIrLowerer::global_expression_inner_type(
  self : WgslIrLowerer,
  expression : Expression,
) -> Handle? {
  match expression {
    Constant(handle) =>
      match self.shader_module.constants.items.get(handle.index()) {
        Some(constant) => Some(constant.ty)
        None => None
      }
    Override(handle) =>
      match self.shader_module.overrides.items.get(handle.index()) {
        Some(override_) => Some(override_.ty)
        None => None
      }
    GlobalVariable(handle) =>
      match self.shader_module.global_variables.items.get(handle.index()) {
        Some(global) => Some(global.ty)
        None => None
      }
    Compose(ty, _) | ZeroValue(ty) => Some(ty)
    AccessIndex(base, index) =>
      match self.global_expression_type(base) {
        Some(ty) => self.indexed_type_from_type_handle(ty, Some(index))
        None => None
      }
    Component(base, _) =>
      match self.global_expression_type(base) {
        Some(ty) => self.indexed_type_from_type_handle(ty, Some(0))
        None => None
      }
    Access(base, _) =>
      match self.global_expression_type(base) {
        Some(ty) => self.indexed_type_from_type_handle(ty, None)
        None => None
      }
    Splat(size, inner) =>
      match self.global_expression_type(inner) {
        Some(ty) => self.vector_type_from_scalar_type_handle(size, ty)
        None => None
      }
    Swizzle(_, _, _) => None
    Literal(literal) => self.literal_type(literal)
    Unary(_, inner) => self.global_expression_type(inner)
    Binary(op, left, right) =>
      match self.global_expression_type(left) {
        Some(left_ty) =>
          match self.global_expression_type(right) {
            Some(right_ty) =>
              Some(self.binary_expression_result_type(op, left_ty, right_ty))
            None => Some(left_ty)
          }
        None => self.global_expression_type(right)
      }
    Select(_, accept, reject) =>
      match self.global_expression_type(accept) {
        Some(ty) => Some(ty)
        None => self.global_expression_type(reject)
      }
    Bitcast(ty, _) => Some(ty)
    Math(math_function, a, b, c, d) =>
      self.math_expression_result_type(
        Function(None),
        math_function,
        a,
        b,
        c,
        d,
      )
    Relational(relational_function, expr) =>
      self.relational_expression_result_type(
        Function(None),
        relational_function,
        expr,
      )
    As(expr, kind, width) =>
      self.as_expression_result_type(
        self.global_expression_type(expr),
        kind,
        width.unwrap_or(4),
      )
    _ => None
  }
}

///|
fn WgslIrLowerer::deref_pointer_type_handle(
  self : WgslIrLowerer,
  ty_handle : Handle,
) -> Handle {
  match self.shader_module.types.get(ty_handle) {
    Some(ty) =>
      match ty.inner {
        Pointer(inner, _) => self.deref_pointer_type_handle(inner)
        _ => ty_handle
      }
    None => ty_handle
  }
}

///|
fn WgslIrLowerer::load_result_type_handle(
  self : WgslIrLowerer,
  ty_handle : Handle,
) -> Handle {
  let deref = self.deref_pointer_type_handle(ty_handle)
  match self.shader_module.types.get(deref) {
    Some(type_) =>
      match type_.inner {
        Atomic(scalar) => self.scalar_type_handle(scalar)
        _ => deref
      }
    None => deref
  }
}

///|
fn WgslIrLowerer::pointer_type_for_lvalue(
  self : WgslIrLowerer,
  function : Function,
  target : Handle,
) -> Handle raise WgslIrLowerError {
  guard self.function_expression_type(function, target) is Some(value_ty) else {
    raise MissingType("address-of target")
  }
  let space = self.address_space_for_lvalue(function, target)
  let key = "ptr<\{wgsl_ir_address_space_key(space)}, \{self.type_key(value_ty)}>"
  let handle = self.shader_module.append_type({
    name: Some(key),
    inner: Pointer(value_ty, space),
  })
  self.type_cache.set(key, handle)
  handle
}

///|
fn WgslIrLowerer::address_space_for_lvalue(
  self : WgslIrLowerer,
  function : Function,
  target : Handle,
) -> AddressSpace {
  match function.expressions.items.get(target.index()) {
    Some(expression) =>
      match expression {
        LocalVariable(handle) =>
          match function.local_variables.items.get(handle.index()) {
            Some(local_variable) =>
              self.address_space_from_pointer_type(local_variable.ty)
            None => Function
          }
        GlobalVariable(handle) =>
          match self.shader_module.global_variables.items.get(handle.index()) {
            Some(global) => global.space
            None => Function
          }
        FunctionArgument(index) =>
          match function.arguments.get(index) {
            Some(argument) => self.address_space_from_pointer_type(argument.ty)
            None => Function
          }
        Access(base, _) | AccessIndex(base, _) =>
          self.address_space_for_lvalue(function, base)
        Load(inner) => self.address_space_for_lvalue(function, inner)
        _ => Function
      }
    None => Function
  }
}

///|
fn WgslIrLowerer::address_space_from_pointer_type(
  self : WgslIrLowerer,
  ty_handle : Handle,
) -> AddressSpace {
  match self.shader_module.types.get(ty_handle) {
    Some(ty) =>
      match ty.inner {
        Pointer(_, space) => space
        _ => Function
      }
    None => Function
  }
}

///|
fn WgslIrLowerer::type_key(self : WgslIrLowerer, ty_handle : Handle) -> String {
  match self.shader_module.types.get(ty_handle) {
    Some(ty) =>
      match ty.name {
        Some(name) => name
        None => "type#\{ty_handle.index()}"
      }
    None => "type#\{ty_handle.index()}"
  }
}

///|
fn WgslIrLowerer::lower_member_expression(
  self : WgslIrLowerer,
  function : Function,
  base_handle : Handle,
  member_name : String,
) -> Handle raise WgslIrLowerError {
  match self.function_expression_type(function, base_handle) {
    Some(ty_handle) =>
      match
        self.shader_module.types.get(self.deref_pointer_type_handle(ty_handle)) {
        Some(ty) =>
          match ty.inner {
            Vector(_, _) =>
              match wgsl_ir_swizzle_components(member_name) {
                Some(components) =>
                  if components.length() == 1 {
                    let base_handle = if self.function_expression_is_reference(
                        function, base_handle,
                      ) {
                      base_handle
                    } else {
                      self.apply_function_load_rule(function, base_handle)
                    }
                    function.expressions.append(
                      AccessIndex(
                        base_handle,
                        wgsl_ir_gather_component_index(components[0]),
                      ),
                    )
                  } else {
                    let value_base = self.apply_function_load_rule(
                      function, base_handle,
                    )
                    function.expressions.append(
                      Swizzle(
                        wgsl_ir_vector_size_from_component_count(
                          components.length(),
                        ),
                        value_base,
                        components,
                      ),
                    )
                  }
                None =>
                  raise UnsupportedExpression(
                    "invalid vector swizzle: \{member_name}",
                  )
              }
            _ =>
              function.expressions.append(
                AccessIndex(
                  base_handle,
                  self.member_index_for_expression(
                    function, base_handle, member_name,
                  ),
                ),
              )
          }
        None =>
          function.expressions.append(
            AccessIndex(
              base_handle,
              self.member_index_for_expression(
                function, base_handle, member_name,
              ),
            ),
          )
      }
    None =>
      function.expressions.append(
        AccessIndex(
          base_handle,
          self.member_index_for_expression(function, base_handle, member_name),
        ),
      )
  }
}

///|
fn WgslIrLowerer::apply_function_load_rule(
  self : WgslIrLowerer,
  function : Function,
  handle : Handle,
) -> Handle {
  if self.function_expression_is_reference(function, handle) {
    function.expressions.append(Load(handle))
  } else {
    handle
  }
}

///|
fn WgslIrLowerer::function_expression_is_reference(
  self : WgslIrLowerer,
  function : Function,
  handle : Handle,
) -> Bool {
  match function.expressions.items.get(handle.index()) {
    Some(expression) =>
      match expression {
        FunctionArgument(index) =>
          self.function_argument_is_pointer(function, index)
        LocalVariable(local_handle) =>
          match function.local_variables.items.get(local_handle.index()) {
            Some(local_variable) => local_variable.kind == Var
            None => false
          }
        GlobalVariable(global_handle) =>
          !self.global_variable_is_handle_value(global_handle)
        Access(base, _) | AccessIndex(base, _) | Swizzle(_, base, _) =>
          self.function_expression_is_reference(function, base) ||
          self.function_expression_has_pointer_type(function, base)
        Load(_) | AddressOf(_, _) => false
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::function_expression_has_pointer_type(
  self : WgslIrLowerer,
  function : Function,
  handle : Handle,
) -> Bool {
  match self.function_expression_type(function, handle) {
    Some(ty) => self.lowerer_type_is_pointer(ty)
    None => false
  }
}

///|
fn WgslIrLowerer::function_expression_type(
  self : WgslIrLowerer,
  function : Function,
  handle : Handle,
) -> Handle? {
  match wgsl_ir_function_named_expression_type(function, handle) {
    Some(ty) => return Some(ty)
    None => ()
  }
  match function.expressions.items.get(handle.index()) {
    Some(expression) =>
      self.function_expression_inner_type(function, expression)
    None => None
  }
}

///|
fn WgslIrLowerer::function_expression_inner_type(
  self : WgslIrLowerer,
  function : Function,
  expression : Expression,
) -> Handle? {
  match expression {
    FunctionArgument(index) =>
      match function.arguments.get(index) {
        Some(argument) => Some(argument.ty)
        None => None
      }
    LocalVariable(handle) =>
      match function.local_variables.items.get(handle.index()) {
        Some(local_variable) => Some(local_variable.ty)
        None => None
      }
    GlobalVariable(handle) =>
      match self.shader_module.global_variables.items.get(handle.index()) {
        Some(global) => Some(global.ty)
        None => None
      }
    Constant(handle) =>
      match self.shader_module.constants.items.get(handle.index()) {
        Some(constant) => Some(constant.ty)
        None => None
      }
    Override(handle) =>
      match self.shader_module.overrides.items.get(handle.index()) {
        Some(override_) => Some(override_.ty)
        None => None
      }
    Load(inner) =>
      match self.function_expression_type(function, inner) {
        Some(ty) => Some(self.load_result_type_handle(ty))
        None => None
      }
    AddressOf(ty, _) => Some(ty)
    Compose(ty, _) | ZeroValue(ty) => Some(ty)
    AccessIndex(base, index) =>
      self.indexed_expression_type(function, base, Some(index))
    Component(base, _) => self.indexed_expression_type(function, base, Some(0))
    Access(base, _) => self.indexed_expression_type(function, base, None)
    Splat(size, inner) =>
      match self.function_expression_type(function, inner) {
        Some(ty) => self.vector_type_from_scalar_type_handle(size, ty)
        None => None
      }
    Swizzle(size, base, _) => self.swizzle_expression_type(function, base, size)
    Unary(_, inner) => self.function_expression_type(function, inner)
    Binary(op, left, right) =>
      match self.function_expression_type(function, left) {
        Some(left_ty) =>
          match self.function_expression_type(function, right) {
            Some(right_ty) =>
              Some(self.binary_expression_result_type(op, left_ty, right_ty))
            None => Some(left_ty)
          }
        None => self.function_expression_type(function, right)
      }
    Select(_, accept, reject) =>
      match self.function_expression_type(function, accept) {
        Some(ty) => Some(ty)
        None => self.function_expression_type(function, reject)
      }
    Bitcast(ty, _) => Some(ty)
    FunctionCall(handle, _) | CallResult(handle) =>
      match self.shader_module.functions.items.get(handle.index()) {
        Some(callee) =>
          match callee.result {
            Some(result) => Some(result.ty)
            None => None
          }
        None => None
      }
    Derivative(_, _, expr) => self.function_expression_type(function, expr)
    Math(math_function, a, b, c, d) =>
      self.math_expression_result_type(function, math_function, a, b, c, d)
    Relational(relational_function, expr) =>
      self.relational_expression_result_type(
        function, relational_function, expr,
      )
    As(expr, kind, width) =>
      self.as_expression_result_type(
        self.function_expression_type(function, expr),
        kind,
        width.unwrap_or(4),
      )
    AtomicCall(atomic_function, arguments) =>
      self.atomic_expression_result_type(function, atomic_function, arguments)
    AtomicResult(ty, _) => Some(ty)
    ImageSample(image, _, gather, _, _, _, _, depth_ref, _) =>
      self.image_expression_result_type(function, image, gather, depth_ref)
    ImageLoad(image, _, _, _, _) =>
      self.image_expression_result_type(function, image, None, None)
    ImageQuery(image, query) =>
      self.image_query_expression_result_type(function, image, query)
    ArrayLength(_) => self.type_cache.get("u32")
    RayQueryGetIntersection(_, _) => self.type_cache.get("RayIntersection")
    RayQueryVertexPositions(_, _) =>
      self.shader_module.special_types.ray_vertex_return
    RayQueryProceedResult => self.type_cache.get("bool")
    WorkGroupUniformLoad(pointer) =>
      match self.function_expression_type(function, pointer) {
        Some(ty) => Some(self.deref_pointer_type_handle(ty))
        None => None
      }
    CooperativeLoad(columns, rows, role, data) =>
      match self.cooperative_data_scalar(function, data) {
        Some(scalar) =>
          Some(self.cooperative_matrix_type_handle(columns, rows, scalar, role))
        None => None
      }
    CooperativeMultiplyAdd(_, _, c) =>
      self.function_expression_type(function, c)
    SubgroupCall(subgroup_function, arguments) =>
      self.subgroup_expression_result_type(
        function, subgroup_function, arguments,
      )
    Literal(literal) => self.literal_type(literal)
    _ => None
  }
}

///|
fn WgslIrLowerer::as_expression_result_type(
  self : WgslIrLowerer,
  input : Handle?,
  kind : ScalarKind,
  width : Int32,
) -> Handle? {
  guard input is Some(input_ty) else { return None }
  let scalar = Scalar::Scalar(kind, width)
  match self.shader_module.types.get(input_ty) {
    Some(type_) =>
      match type_.inner {
        Scalar(_) => Some(self.scalar_type_handle(scalar))
        Vector(size, _) => Some(self.vector_type_handle(size, scalar))
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::subgroup_expression_result_type(
  self : WgslIrLowerer,
  function : Function,
  subgroup_function : SubgroupFunction,
  arguments : Array[Handle],
) -> Handle? {
  match subgroup_function {
    Ballot => Some(self.vector_type_handle(Quad, Scalar(Uint, 4)))
    All | Any => self.type_cache.get("bool")
    _ =>
      match arguments.get(0) {
        Some(first) => self.function_expression_type(function, first)
        None => None
      }
  }
}

///|
fn WgslIrLowerer::math_expression_result_type(
  self : WgslIrLowerer,
  function : Function,
  math_function : MathFunction,
  argument : Handle,
  b : Handle?,
  c : Handle?,
  d : Handle?,
) -> Handle? {
  guard self.function_expression_type(function, argument) is Some(input_ty) else {
    return None
  }
  match math_function {
    Modf => return self.predeclared_modf_result_type(input_ty)
    Frexp => return self.predeclared_frexp_result_type(input_ty)
    Pack4x8snorm
    | Pack4x8unorm
    | Pack2x16snorm
    | Pack2x16unorm
    | Pack2x16float
    | Pack4xI8
    | Pack4xU8
    | Pack4xI8Clamp
    | Pack4xU8Clamp
    | Dot4U8Packed => return Some(self.scalar_type_handle(Scalar(Uint, 4)))
    Dot4I8Packed => return Some(self.scalar_type_handle(Scalar(Sint, 4)))
    Unpack4x8snorm | Unpack4x8unorm =>
      return Some(self.vector_type_handle(Quad, Scalar(Float, 4)))
    Unpack2x16snorm | Unpack2x16unorm | Unpack2x16float =>
      return Some(self.vector_type_handle(Bi, Scalar(Float, 4)))
    Unpack4xI8 => return Some(self.vector_type_handle(Quad, Scalar(Sint, 4)))
    Unpack4xU8 => return Some(self.vector_type_handle(Quad, Scalar(Uint, 4)))
    Dot | Distance | Length | Determinant =>
      return Some(self.scalar_type_from_composite_type(input_ty))
    _ => ()
  }
  if wgsl_ir_math_function_uses_uniform_numeric_argument_context(math_function) {
    match
      wgsl_ir_math_primary_result_operand(math_function, argument, b, c, d) {
      Some(primary) =>
        if self.function_expression_has_concrete_component_scalar(
            function, primary,
          ) {
          return self.function_expression_type(function, primary)
        }
      None => ()
    }
    let arguments : Array[Handle] = [argument]
    match b {
      Some(value) => arguments.push(value)
      None => ()
    }
    match c {
      Some(value) => arguments.push(value)
      None => ()
    }
    match d {
      Some(value) => arguments.push(value)
      None => ()
    }
    for value in arguments {
      match self.function_expression_type(function, value) {
        Some(ty) =>
          match self.scalar_from_numeric_type_handle(ty) {
            Some(scalar) if !wgsl_ir_scalar_kind_is_abstract(scalar.kind) =>
              return Some(ty)
            _ => ()
          }
        None => ()
      }
    }
  }
  match math_function {
    Transpose => self.transpose_expression_result_type(input_ty)
    _ => Some(input_ty)
  }
}

///|
fn WgslIrLowerer::function_expression_has_concrete_component_scalar(
  self : WgslIrLowerer,
  function : Function,
  handle : Handle,
) -> Bool {
  match self.function_expression_type(function, handle) {
    Some(ty) =>
      match self.scalar_for_literal_context_type(ty) {
        Some(scalar) => !wgsl_ir_scalar_kind_is_abstract(scalar.kind)
        None => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::transpose_expression_result_type(
  self : WgslIrLowerer,
  input_ty : Handle,
) -> Handle? {
  match self.shader_module.types.get(input_ty) {
    Some(type_) =>
      match type_.inner {
        Matrix(columns, rows, scalar) =>
          Some(self.matrix_type_handle(rows, columns, scalar))
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::relational_expression_result_type(
  self : WgslIrLowerer,
  function : Function,
  relational_function : RelationalFunction,
  argument : Handle,
) -> Handle? {
  match relational_function {
    All | Any => Some(self.scalar_type_handle(Scalar(Bool, 1)))
    IsNan | IsInf =>
      match self.function_expression_type(function, argument) {
        Some(input_ty) => Some(self.boolean_result_type(input_ty, input_ty))
        None => None
      }
  }
}

///|
fn WgslIrLowerer::binary_expression_result_type(
  self : WgslIrLowerer,
  op : BinaryOperator,
  left_ty : Handle,
  right_ty : Handle,
) -> Handle {
  if wgsl_ir_binary_operator_is_comparison(op) {
    return self.boolean_result_type(left_ty, right_ty)
  }
  if op == Multiply {
    match self.matrix_vector_multiply_result_type(left_ty, right_ty) {
      Some(ty) => return ty
      None => ()
    }
  }
  if wgsl_ir_binary_operator_is_arithmetic(op) {
    match self.numeric_binary_scalar_result_type(left_ty, right_ty) {
      Some(ty) => return ty
      None => ()
    }
  }
  if self.type_is_scalar(left_ty) && !self.type_is_scalar(right_ty) {
    return right_ty
  }
  if !self.type_is_scalar(left_ty) && self.type_is_scalar(right_ty) {
    return left_ty
  }
  left_ty
}

///|
fn WgslIrLowerer::matrix_vector_multiply_result_type(
  self : WgslIrLowerer,
  left_ty : Handle,
  right_ty : Handle,
) -> Handle? {
  match self.shader_module.types.get(left_ty) {
    Some(left) =>
      match left.inner {
        Matrix(left_columns, rows, scalar) =>
          match self.shader_module.types.get(right_ty) {
            Some(right) =>
              match right.inner {
                Vector(_, _) => Some(self.vector_type_handle(rows, scalar))
                Matrix(columns, right_rows, _) =>
                  if right_rows == left_columns {
                    Some(self.matrix_type_handle(columns, rows, scalar))
                  } else {
                    None
                  }
                _ => None
              }
            None => None
          }
        Vector(_, scalar) =>
          match self.shader_module.types.get(right_ty) {
            Some(right) =>
              match right.inner {
                Matrix(columns, _, _) =>
                  Some(self.vector_type_handle(columns, scalar))
                _ => None
              }
            None => None
          }
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::vector_type_handle(
  self : WgslIrLowerer,
  size : VectorSize,
  scalar : Scalar,
) -> Handle {
  self.intern_type(
    "vec\{size.width()}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
    Vector(size, scalar),
  )
}

///|
fn WgslIrLowerer::vector_type_from_scalar_type_handle(
  self : WgslIrLowerer,
  size : VectorSize,
  ty : Handle,
) -> Handle? {
  match self.shader_module.types.get(ty) {
    Some({ inner: Scalar(scalar), .. }) =>
      Some(self.vector_type_handle(size, scalar))
    _ => None
  }
}

///|
fn WgslIrLowerer::matrix_type_handle(
  self : WgslIrLowerer,
  columns : VectorSize,
  rows : VectorSize,
  scalar : Scalar,
) -> Handle {
  self.intern_type(
    "mat\{columns.width()}x\{rows.width()}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
    Matrix(columns, rows, scalar),
  )
}

///|
fn WgslIrLowerer::boolean_result_type(
  self : WgslIrLowerer,
  left_ty : Handle,
  right_ty : Handle,
) -> Handle {
  match self.vector_size_from_type(left_ty) {
    Some(size) => self.boolean_vector_type(size)
    None =>
      match self.vector_size_from_type(right_ty) {
        Some(size) => self.boolean_vector_type(size)
        None => self.scalar_type_handle(Scalar(Bool, 1))
      }
  }
}

///|
fn WgslIrLowerer::boolean_vector_type(
  self : WgslIrLowerer,
  size : VectorSize,
) -> Handle {
  self.intern_type("vec\{size.width()}", Vector(size, Scalar(Bool, 1)))
}

///|
fn WgslIrLowerer::type_is_scalar(
  self : WgslIrLowerer,
  ty_handle : Handle,
) -> Bool {
  match self.shader_module.types.get(ty_handle) {
    Some(ty) =>
      match ty.inner {
        Scalar(_) => true
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::type_is_vector(
  self : WgslIrLowerer,
  ty_handle : Handle,
  size : VectorSize,
  scalar : Scalar,
) -> Bool {
  match self.shader_module.types.get(ty_handle) {
    Some(type_) =>
      match type_.inner {
        Vector(existing_size, existing_scalar) =>
          existing_size == size && existing_scalar == scalar
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::identity_vector_constructor_argument(
  self : WgslIrLowerer,
  ty : Handle,
  arguments : Array[Handle],
  function : Function,
) -> Handle? {
  guard arguments.length() == 1 else { return None }
  guard self.shader_module.types.get(ty) is Some(type_) else { return None }
  match type_.inner {
    Vector(size, scalar) =>
      match self.function_expression_type(function, arguments[0]) {
        Some(argument_ty) =>
          if self.type_is_vector(argument_ty, size, scalar) {
            Some(arguments[0])
          } else {
            None
          }
        None => None
      }
    _ => None
  }
}

///|
fn WgslIrLowerer::vector_size_from_type(
  self : WgslIrLowerer,
  ty_handle : Handle,
) -> VectorSize? {
  match self.shader_module.types.get(ty_handle) {
    Some(ty) =>
      match ty.inner {
        Vector(size, _) => Some(size)
        _ => None
      }
    None => None
  }
}

///|
pub fn wgsl_ir_binary_operator_is_comparison(op : BinaryOperator) -> Bool {
  match op {
    Equal | NotEqual | Less | LessEqual | Greater | GreaterEqual => true
    _ => false
  }
}

///|
fn WgslIrLowerer::atomic_expression_result_type(
  self : WgslIrLowerer,
  function : Function,
  atomic_function : AtomicFunction,
  arguments : Array[Handle],
) -> Handle? {
  guard arguments is [_, ..] else { return None }
  let scalar = self.atomic_call_result_scalar_type(function, arguments)
  match atomic_function {
    Store => None
    CompareExchangeWeak =>
      match scalar {
        Some(value) =>
          Some(
            self.predeclared_struct_type(AtomicCompareExchangeWeakResult(value)),
          )
        None => None
      }
    _ =>
      match scalar {
        Some(value) => Some(self.scalar_type_handle(value))
        None => None
      }
  }
}

///|
fn WgslIrLowerer::atomic_pointer_scalar_type(
  self : WgslIrLowerer,
  function : Function,
  pointer : Handle,
) -> Scalar? {
  guard self.function_expression_type(function, pointer) is Some(ty_handle) else {
    return None
  }
  self.atomic_scalar_type_from_handle(ty_handle)
}

///|
fn WgslIrLowerer::atomic_scalar_type_from_handle(
  self : WgslIrLowerer,
  ty_handle : Handle,
) -> Scalar? {
  match self.shader_module.types.items.get(ty_handle.index()) {
    Some(ty) =>
      match ty.inner {
        Atomic(scalar) => Some(scalar)
        Pointer(base, _) => self.atomic_scalar_type_from_handle(base)
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::predeclared_numeric_result_type(
  self : WgslIrLowerer,
  input_ty : Handle,
  frexp : Bool,
) -> Handle? {
  match self.shader_module.types.items.get(input_ty.index()) {
    Some(ty) =>
      match ty.inner {
        Scalar(scalar) =>
          if frexp {
            Some(self.predeclared_struct_type(FrexpResult(None, scalar)))
          } else {
            Some(self.predeclared_struct_type(ModfResult(None, scalar)))
          }
        Vector(size, scalar) =>
          if frexp {
            Some(self.predeclared_struct_type(FrexpResult(Some(size), scalar)))
          } else {
            Some(self.predeclared_struct_type(ModfResult(Some(size), scalar)))
          }
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::predeclared_modf_result_type(
  self : WgslIrLowerer,
  input_ty : Handle,
) -> Handle? {
  self.predeclared_numeric_result_type(input_ty, false)
}

///|
fn WgslIrLowerer::predeclared_frexp_result_type(
  self : WgslIrLowerer,
  input_ty : Handle,
) -> Handle? {
  self.predeclared_numeric_result_type(input_ty, true)
}

///|
fn WgslIrLowerer::predeclared_struct_type(
  self : WgslIrLowerer,
  predeclared : PredeclaredType,
) -> Handle {
  for item in self.shader_module.special_types.predeclared_types {
    let (existing, handle) = item
    if existing == predeclared {
      return handle
    }
  }
  let handle = self.shader_module.append_type({
    name: None,
    inner: self.predeclared_type_inner(predeclared),
  })
  self.shader_module.special_types.predeclared_types.push((predeclared, handle))
  handle
}

///|
fn WgslIrLowerer::predeclared_type_inner(
  self : WgslIrLowerer,
  predeclared : PredeclaredType,
) -> TypeInner {
  match predeclared {
    AtomicCompareExchangeWeakResult(scalar) => {
      let value_ty = self.scalar_type_handle(scalar)
      let bool_ty = self.scalar_type_handle(Scalar(Bool, 1))
      let value_layout = WgslIrLayout::{
        align: scalar.width,
        size: scalar.width,
      }
      let bool_layout = WgslIrLayout::{ align: 1, size: 1, }
      let exchanged_offset = wgsl_ir_align_to(
        value_layout.size,
        bool_layout.align,
      )
      let span = wgsl_ir_align_to(
        exchanged_offset + bool_layout.size,
        value_layout.align,
      )
      Struct(
        [
          {
            name: Some("old_value"),
            ty: value_ty,
            binding: None,
            offset: 0,
            size: None,
            align: None,
          },
          {
            name: Some("exchanged"),
            ty: bool_ty,
            binding: None,
            offset: exchanged_offset,
            size: None,
            align: None,
          },
        ],
        span,
      )
    }
    ModfResult(size, scalar) => {
      let value_ty = self.numeric_value_type_handle(size, scalar)
      let value_layout = wgsl_ir_numeric_value_layout(size, scalar)
      let whole_offset = wgsl_ir_align_to(value_layout.size, value_layout.align)
      let span = wgsl_ir_align_to(
        whole_offset + value_layout.size,
        value_layout.align,
      )
      Struct(
        [
          {
            name: Some("fract"),
            ty: value_ty,
            binding: None,
            offset: 0,
            size: None,
            align: None,
          },
          {
            name: Some("whole"),
            ty: value_ty,
            binding: None,
            offset: whole_offset,
            size: None,
            align: None,
          },
        ],
        span,
      )
    }
    FrexpResult(size, scalar) => {
      let fract_ty = self.numeric_value_type_handle(size, scalar)
      let exponent_scalar = Scalar::Scalar(Sint, 4)
      let exponent_ty = self.numeric_value_type_handle(size, exponent_scalar)
      let fract_layout = wgsl_ir_numeric_value_layout(size, scalar)
      let exponent_layout = wgsl_ir_numeric_value_layout(size, exponent_scalar)
      let exponent_offset = wgsl_ir_align_to(
        fract_layout.size,
        exponent_layout.align,
      )
      let max_align = if fract_layout.align > exponent_layout.align {
        fract_layout.align
      } else {
        exponent_layout.align
      }
      let span = wgsl_ir_align_to(
        exponent_offset + exponent_layout.size,
        max_align,
      )
      Struct(
        [
          {
            name: Some("fract"),
            ty: fract_ty,
            binding: None,
            offset: 0,
            size: None,
            align: None,
          },
          {
            name: Some("exp"),
            ty: exponent_ty,
            binding: None,
            offset: exponent_offset,
            size: None,
            align: None,
          },
        ],
        span,
      )
    }
  }
}

///|
fn wgsl_ir_numeric_value_layout(
  size : VectorSize?,
  scalar : Scalar,
) -> WgslIrLayout {
  match size {
    Some(vector_size) => {
      let width = vector_size.width()
      let raw = width * scalar.width
      { align: if width == 3 { 4 * scalar.width } else { raw }, size: raw, }
    }
    None => { align: scalar.width, size: scalar.width, }
  }
}

///|
fn WgslIrLowerer::numeric_value_type_handle(
  self : WgslIrLowerer,
  size : VectorSize?,
  scalar : Scalar,
) -> Handle {
  match size {
    Some(vector_size) =>
      self.intern_type(
        "vec\{vector_size.width()}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
        Vector(vector_size, scalar),
      )
    None => self.scalar_type_handle(scalar)
  }
}

///|
fn WgslIrLowerer::swizzle_expression_type(
  self : WgslIrLowerer,
  function : Function,
  base : Handle,
  size : VectorSize,
) -> Handle? {
  guard self.function_expression_type(function, base) is Some(base_ty) else {
    return None
  }
  match self.shader_module.types.get(base_ty) {
    Some(ty) =>
      match ty.inner {
        Vector(_, scalar) =>
          Some(
            self.intern_type(
              "vec\{size.width()}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
              Vector(size, scalar),
            ),
          )
        _ => None
      }
    None => None
  }
}

///|