///|
fn WgslIrLowerer::predeclare_function_source_local_prefix(
  self : WgslIrLowerer,
  block : @ast.WgslBlock,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  self.predeclare_function_source_var_const_initializers(block, function, scope)
}

///|
fn WgslIrLowerer::predeclare_function_source_var_const_initializers(
  self : WgslIrLowerer,
  block : @ast.WgslBlock,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  for statement in block.statements() {
    for local_decl in statement.local_bindings() {
      self.predeclare_source_var_const_initializer_expression(
        local_decl, function, scope,
      )
    }
    guard wgsl_ir_statement_allows_function_entry_const_initializer_predeclare(
      statement,
    ) else {
      continue
    }
    for child in statement.blocks() {
      self.predeclare_function_source_var_const_initializers(
        child, function, scope,
      )
    }
  }
}

///|
fn wgsl_ir_statement_allows_function_entry_const_initializer_predeclare(
  statement : @ast.WgslStatement,
) -> Bool {
  match statement.kind() {
    For | While | Loop | Continuing => false
    _ => true
  }
}

///|
fn WgslIrLowerer::predeclare_source_var_const_initializer_expression(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  guard local_decl.kind() == Var else { return }
  guard local_decl.initializer_ref() is Some(expr_ref) else { return }
  let initializer_is_const = match expr_ref.root() {
    Some(root) => self.expression_node_is_const_initializer(root)
    None => false
  }
  guard initializer_is_const else { return }
  let explicit_ty = match local_decl.ty_ref() {
    Some(ty_ref) => Some(self.lower_function_type_ref(ty_ref, function, scope))
    None => None
  }
  let expected_ty = self.source_local_const_initializer_expected_type(
    local_decl, explicit_ty,
  )
  ignore(
    self.lower_source_local_const_initializer(
      expr_ref, expected_ty, function, scope,
    ),
  )
}

///|
fn WgslIrLowerer::source_local_const_initializer_expected_type(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  explicit_ty : Handle?,
) -> Handle? raise WgslIrLowerError {
  let ty = match explicit_ty {
    Some(ty) => Some(ty)
    None =>
      Some(
        self.infer_local_type_from_initializer(
          local_decl.name().name(),
          local_decl.initializer_ref(),
        ),
      )
  }
  match ty {
    Some(value) =>
      if self.source_local_const_initializer_uses_storage_expected_type(value) {
        Some(self.local_declaration_storage_type(local_decl.kind(), value))
      } else {
        Some(value)
      }
    None => None
  }
}

///|
fn WgslIrLowerer::source_local_const_initializer_uses_storage_expected_type(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.shader_module.types.items.get(ty.index()) {
    Some(type_) =>
      match type_.inner {
        Vector(_, _) | Matrix(_, _, _) => true
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::predeclare_source_local_placeholder(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  guard local_decl.kind() == Var else { return }
  let key = wgsl_ir_local_declaration_key(local_decl)
  if scope.predeclared_source_locals.contains(key) {
    return
  }
  let explicit_ty = match local_decl.ty_ref() {
    Some(ty_ref) => Some(self.lower_function_type_ref(ty_ref, function, scope))
    None => None
  }
  let ty = match explicit_ty {
    Some(ty) => ty
    None =>
      match local_decl.initializer_ref() {
        None =>
          self.infer_local_type_from_initializer(
            local_decl.name().name(),
            local_decl.initializer_ref(),
          )
        Some(_) => self.lower_builtin_type_name("i32")
      }
  }
  let ty = self.local_declaration_storage_type(local_decl.kind(), ty)
  let handle = function.local_variables.append({
    name: Some(local_decl.name().name()),
    kind: Var,
    generated_temporary: false,
    generated_temporary_store_value_reference: false,
    short_circuit_result: false,
    ty,
    init: None,
  })
  scope.predeclared_source_locals.set(key, handle)
}

///|
fn WgslIrLowerer::lower_source_local_const_initializer(
  self : WgslIrLowerer,
  expr_ref : @ast.WgslExprRef,
  explicit_ty : Handle?,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let semantic_key = self.source_local_const_initializer_semantic_key(
    expr_ref, explicit_ty,
  )
  match semantic_key {
    Some(key) =>
      match scope.source_local_const_initializer_expressions.get(key) {
        Some(handle) => {
          wgsl_ir_mark_source_local_const_initializer_origin(function, handle)
          return handle
        }
        None => ()
      }
    None => ()
  }
  match expr_ref.root() {
    Some(root) =>
      match wgsl_ir_expr_node_literal_token(root) {
        Some(token) =>
          match
            scope.source_local_const_initializer_expressions.get(
              wgsl_ir_source_local_const_literal_key(token, explicit_ty),
            ) {
            Some(handle) => {
              wgsl_ir_mark_source_local_const_initializer_origin(
                function, handle,
              )
              return handle
            }
            None => ()
          }
        None => ()
      }
    None => ()
  }
  let key = match semantic_key {
    Some(value) => value
    None => wgsl_ir_source_local_const_initializer_key(expr_ref, explicit_ty)
  }
  match scope.source_local_const_initializer_expressions.get(key) {
    Some(handle) => {
      wgsl_ir_mark_source_local_const_initializer_origin(function, handle)
      handle
    }
    None => {
      let expression_mark = function.expressions.items.length()
      let handle = match
        self.lower_reused_literal_source_local_const_constructor_initializer(
          expr_ref, explicit_ty, function, scope,
        ) {
        Some(handle) => handle
        None =>
          self.lower_function_expression_ref_with_expected(
            expr_ref, function, scope, explicit_ty,
          )
      }
      scope.source_local_const_initializer_expressions.set(key, handle)
      wgsl_ir_mark_source_local_const_initializer_origin(function, handle)
      self.record_source_local_const_initializer_literals(
        expr_ref, expression_mark, function, scope, explicit_ty,
      )
      handle
    }
  }
}

///|
fn WgslIrLowerer::lower_reused_literal_source_local_const_constructor_initializer(
  self : WgslIrLowerer,
  expr_ref : @ast.WgslExprRef,
  explicit_ty : Handle?,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle? raise WgslIrLowerError {
  guard explicit_ty is Some(ty) else { return None }
  guard expr_ref.root() is Some(root) else { return None }
  guard root.kind() is Call(callee, arguments) else { return None }
  guard self.expression_callee_is_const_constructor(callee) else { return None }
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard !arguments.is_empty() else { return None }
  if self.source_local_const_constructor_initializer_is_vector_splat(
      ty,
      arguments.length(),
    ) {
    guard arguments is [argument] else { return None }
    guard argument.kind() is Literal(token) else { return None }
    guard self.source_local_const_constructor_initializer_vector_splat_size(
        ty,
        arguments.length(),
      )
      is Some(size) else {
      return None
    }
    guard self.constructor_component_expected_type(ty) is Some(component_ty) else {
      return None
    }
    match
      self.source_local_const_initializer_literal_handle(
        token,
        None,
        function,
        scope,
      ) {
      Some(handle) =>
        return Some(function.expressions.append(Splat(size, handle)))
      None => ()
    }
    match
      self.source_local_const_initializer_literal_handle(
        token,
        Some(component_ty),
        function,
        scope,
      ) {
      Some(handle) =>
        return Some(function.expressions.append(Splat(size, handle)))
      None => return None
    }
  }
  let literal_handles : Map[String, Handle] = Map([])
  self.lower_reused_literal_source_local_const_constructor_node(
    root, ty, literal_handles, function,
  )
}

///|
fn WgslIrLowerer::source_local_const_initializer_literal_handle(
  self : WgslIrLowerer,
  token : @ast.WgslExprToken,
  ty : Handle?,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle? raise WgslIrLowerError {
  let text_key = wgsl_ir_source_local_const_literal_key(token, ty)
  match scope.source_local_const_initializer_expressions.get(text_key) {
    Some(handle) => {
      wgsl_ir_mark_source_local_const_initializer_origin(function, handle)
      return Some(handle)
    }
    None => ()
  }
  match scope.runtime_source_local_const_initializer_expressions.get(text_key) {
    Some(handle) => return Some(handle)
    None => ()
  }
  match self.source_local_const_literal_semantic_key(token, ty) {
    Some(semantic_key) => {
      match scope.source_local_const_initializer_expressions.get(semantic_key) {
        Some(handle) => {
          wgsl_ir_mark_source_local_const_initializer_origin(function, handle)
          return Some(handle)
        }
        None => ()
      }
      match
        scope.runtime_source_local_const_initializer_expressions.get(
          semantic_key,
        ) {
        Some(handle) => return Some(handle)
        None => ()
      }
    }
    None => ()
  }
  match self.source_local_const_literal_abstract_context_type(ty) {
    Some(abstract_ty) =>
      match
        self.source_local_const_initializer_literal_handle(
          token,
          Some(abstract_ty),
          function,
          scope,
        ) {
        Some(handle) => return Some(handle)
        None => ()
      }
    None => ()
  }
  None
}

///|
fn WgslIrLowerer::source_local_const_literal_abstract_context_type(
  self : WgslIrLowerer,
  ty : Handle?,
) -> Handle? {
  guard ty is Some(handle) else { return None }
  match self.shader_module.types.get(handle) {
    Some({ inner: Scalar({ kind: Float, .. }), .. }) =>
      Some(self.scalar_type_handle(Scalar(AbstractFloat, 8)))
    Some({ inner: Scalar({ kind: Sint | Uint, .. }), .. }) =>
      Some(self.scalar_type_handle(Scalar(AbstractInt, 8)))
    _ => None
  }
}

///|
fn WgslIrLowerer::lower_reused_literal_source_local_const_constructor_node(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  expected_ty : Handle,
  literal_handles : Map[String, Handle],
  function : Function,
) -> Handle? raise WgslIrLowerError {
  match node.kind() {
    Literal(token) =>
      Some(
        self.reused_source_local_const_constructor_literal_handle(
          token,
          Some(expected_ty),
          literal_handles,
          function,
        ),
      )
    Call(callee, arguments) => {
      guard self.expression_callee_is_const_constructor(callee) else {
        return None
      }
      let arguments = wgsl_ir_flatten_expression_arguments(arguments)
      guard !arguments.is_empty() else { return None }
      let components : Array[Handle] = []
      for index, argument in arguments {
        let argument_ty = match
          self.constructor_literal_argument_expected_type(
            expected_ty,
            index,
            arguments.length(),
          ) {
          Some(value) => value
          None => expected_ty
        }
        match
          self.lower_reused_literal_source_local_const_constructor_node(
            argument, argument_ty, literal_handles, function,
          ) {
          Some(handle) => components.push(handle)
          None => return None
        }
      }
      match
        self.source_local_const_constructor_initializer_vector_splat_size(
          expected_ty,
          arguments.length(),
        ) {
        Some(size) =>
          Some(
            wgsl_ir_reused_source_local_const_constructor_expression_handle(
              "splat:\{expected_ty.index()}:\{components[0].index()}",
              Splat(size, components[0]),
              literal_handles,
              function,
            ),
          )
        None =>
          Some(
            wgsl_ir_reused_source_local_const_constructor_expression_handle(
              "compose:\{expected_ty.index()}:\{wgsl_ir_handle_index_list_key(components)}",
              Compose(expected_ty, components),
              literal_handles,
              function,
            ),
          )
      }
    }
    Group(inner) =>
      self.lower_reused_literal_source_local_const_constructor_node(
        inner, expected_ty, literal_handles, function,
      )
    Sequence([single]) =>
      self.lower_reused_literal_source_local_const_constructor_node(
        single, expected_ty, literal_handles, function,
      )
    _ => None
  }
}

///|
fn wgsl_ir_reused_source_local_const_constructor_expression_handle(
  key : String,
  expression : Expression,
  literal_handles : Map[String, Handle],
  function : Function,
) -> Handle {
  match literal_handles.get(key) {
    Some(existing) => existing
    None => {
      let appended = function.expressions.append(expression)
      literal_handles.set(key, appended)
      appended
    }
  }
}

///|
fn wgsl_ir_handle_index_list_key(handles : Array[Handle]) -> String {
  let parts : Array[String] = []
  for handle in handles {
    parts.push(handle.index().to_string())
  }
  parts.join(",")
}

///|
fn WgslIrLowerer::reused_source_local_const_constructor_literal_handle(
  self : WgslIrLowerer,
  token : @ast.WgslExprToken,
  expected_ty : Handle?,
  literal_handles : Map[String, Handle],
  function : Function,
) -> Handle raise WgslIrLowerError {
  let literal_ty = match expected_ty {
    Some(ty) =>
      match self.constructor_component_expected_type(ty) {
        Some(component_ty) => Some(component_ty)
        None => Some(ty)
      }
    None => None
  }
  let literal = self.literal_from_token_with_expected(token, literal_ty)
  let semantic_key = wgsl_ir_source_local_const_literal_value_key(
    literal_ty, literal,
  )
  match literal_handles.get(semantic_key) {
    Some(existing) => existing
    None => {
      let appended = function.expressions.append(Literal(literal))
      literal_handles.set(semantic_key, appended)
      appended
    }
  }
}

///|
fn WgslIrLowerer::source_local_const_constructor_initializer_is_vector_splat(
  self : WgslIrLowerer,
  ty_handle : Handle,
  argument_count : Int32,
) -> Bool {
  guard argument_count == 1 else { return false }
  match self.shader_module.types.get(ty_handle) {
    Some({ inner: Vector(_, _), .. }) => true
    _ => false
  }
}

///|
fn WgslIrLowerer::source_local_const_constructor_initializer_vector_splat_size(
  self : WgslIrLowerer,
  ty_handle : Handle,
  argument_count : Int32,
) -> VectorSize? {
  guard argument_count == 1 else { return None }
  match self.shader_module.types.get(ty_handle) {
    Some({ inner: Vector(size, _), .. }) => Some(size)
    _ => None
  }
}

///|
fn WgslIrLowerer::constructor_literal_argument_expected_type(
  self : WgslIrLowerer,
  ty_handle : Handle,
  argument_index : Int32,
  argument_count : Int32,
) -> Handle? {
  match self.shader_module.types.get(ty_handle) {
    Some(ty) =>
      match ty.inner {
        Vector(size, scalar) =>
          if argument_count == size.width() {
            Some(self.scalar_type_handle(scalar))
          } else {
            None
          }
        Matrix(columns, rows, scalar) =>
          if argument_count == columns.width() {
            Some(self.vector_type_handle(rows, scalar))
          } else if argument_count == columns.width() * rows.width() {
            Some(self.scalar_type_handle(scalar))
          } else {
            None
          }
        Struct(members, _) | PredeclaredStruct(members, _) =>
          match members.get(argument_index) {
            Some(struct_member) => Some(struct_member.ty)
            None => None
          }
        Array(element, _, _) | BindingArray(element, _) => Some(element)
        _ => None
      }
    None => None
  }
}

///|
fn wgsl_ir_expr_node_literal_token(
  node : @ast.WgslExprNode,
) -> @ast.WgslExprToken? {
  match node.kind() {
    Literal(token) => Some(token)
    Group(inner) => wgsl_ir_expr_node_literal_token(inner)
    Sequence([single]) => wgsl_ir_expr_node_literal_token(single)
    _ => None
  }
}

///|
fn WgslIrLowerer::record_source_local_const_initializer_literals(
  self : WgslIrLowerer,
  expr_ref : @ast.WgslExprRef,
  expression_mark : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
  explicit_ty : Handle?,
) -> Unit raise WgslIrLowerError {
  let literal_tokens : Array[@ast.WgslExprToken] = []
  match expr_ref.root() {
    Some(root) => wgsl_ir_collect_expr_node_literal_tokens(root, literal_tokens)
    None => ()
  }
  guard !literal_tokens.is_empty() else { return }
  let mut token_index = 0
  let expression_end = function.expressions.items.length()
  for expression_index in expression_mark.. {
        let literal_handle = Handle::Handle(expression_index)
        function.expressions.set_origin(
          literal_handle,
          SourceLocalConstInitializer,
        )
        let literal_ty = match
          self.function_expression_type(function, literal_handle) {
          Some(ty) => Some(ty)
          None => explicit_ty
        }
        self.record_source_local_const_literal_handle(
          function,
          scope,
          literal_tokens[token_index],
          literal_ty,
          literal_handle,
        )
        match
          self.source_local_const_literal_initializer_context_type(
            function, literal_handle, expression_end, explicit_ty,
          ) {
          Some(context_ty) =>
            self.record_source_local_const_literal_handle(
              function,
              scope,
              literal_tokens[token_index],
              Some(context_ty),
              literal_handle,
            )
          None => ()
        }
        token_index = token_index + 1
      }
      _ => ()
    }
  }
}

///|
fn WgslIrLowerer::source_local_const_literal_initializer_context_type(
  self : WgslIrLowerer,
  function : Function,
  literal : Handle,
  expression_end : Int32,
  explicit_ty : Handle?,
) -> Handle? {
  match self.source_local_const_literal_context_type(explicit_ty) {
    Some(ty) => return Some(ty)
    None => ()
  }
  let start = literal.index() + 1
  for expression_index in start..
        match
          self.function_expression_type(function, Handle(expression_index)) {
          Some(ty) =>
            match self.source_local_const_literal_context_type(Some(ty)) {
              Some(context_ty) => return Some(context_ty)
              None => ()
            }
          None => ()
        }
      Some(Compose(ty, components)) =>
        if wgsl_ir_source_local_const_initializer_components_contain(
            components, literal,
          ) {
          match self.source_local_const_literal_context_type(Some(ty)) {
            Some(context_ty) => return Some(context_ty)
            None => ()
          }
        }
      _ => ()
    }
  }
  None
}

///|
fn wgsl_ir_source_local_const_initializer_components_contain(
  components : Array[Handle],
  literal : Handle,
) -> Bool {
  for component in components {
    if component == literal {
      return true
    }
  }
  false
}

///|
fn WgslIrLowerer::record_source_local_const_literal_handle(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  token : @ast.WgslExprToken,
  ty : Handle?,
  handle : Handle,
) -> Unit raise WgslIrLowerError {
  wgsl_ir_mark_source_local_const_initializer_origin(function, handle)
  let key = wgsl_ir_source_local_const_literal_key(token, ty)
  if !scope.source_local_const_initializer_expressions.contains(key) {
    scope.source_local_const_initializer_expressions.set(key, handle)
  }
  match self.source_local_const_literal_semantic_key(token, ty) {
    Some(semantic_key) =>
      if !scope.source_local_const_initializer_expressions.contains(
          semantic_key,
        ) {
        scope.source_local_const_initializer_expressions.set(
          semantic_key, handle,
        )
      }
    None => ()
  }
}

///|
fn wgsl_ir_mark_source_local_const_initializer_origin(
  function : Function,
  handle : Handle,
) -> Unit {
  match function.expressions.items.get(handle.index()) {
    Some(Literal(_)) =>
      function.expressions.set_origin(handle, SourceLocalConstInitializer)
    _ => ()
  }
}

///|
fn WgslIrLowerer::source_local_const_literal_context_type(
  self : WgslIrLowerer,
  explicit_ty : Handle?,
) -> Handle? {
  match explicit_ty {
    Some(ty) =>
      match self.shader_module.types.get(ty) {
        Some({ inner: Scalar(_), .. }) => Some(ty)
        Some({ inner: Vector(_, scalar), .. })
        | Some({ inner: Matrix(_, _, scalar), .. }) =>
          Some(self.scalar_type_handle(scalar))
        _ => None
      }
    None => None
  }
}

///|
fn wgsl_ir_collect_expr_node_literal_tokens(
  node : @ast.WgslExprNode,
  out : Array[@ast.WgslExprToken],
) -> Unit {
  match node.kind() {
    Literal(token) => out.push(token)
    Group(inner) => wgsl_ir_collect_expr_node_literal_tokens(inner, out)
    Unary(_, inner) => wgsl_ir_collect_expr_node_literal_tokens(inner, out)
    Binary(_, left, right) => {
      wgsl_ir_collect_expr_node_literal_tokens(left, out)
      wgsl_ir_collect_expr_node_literal_tokens(right, out)
    }
    Call(callee, arguments) => {
      wgsl_ir_collect_expr_node_literal_tokens(callee, out)
      for argument in arguments {
        wgsl_ir_collect_expr_node_literal_tokens(argument, out)
      }
    }
    Template(base, _) => wgsl_ir_collect_expr_node_literal_tokens(base, out)
    Member(base, _) => wgsl_ir_collect_expr_node_literal_tokens(base, out)
    PathAccess(base, _) => wgsl_ir_collect_expr_node_literal_tokens(base, out)
    Sequence(items) =>
      for item in items {
        wgsl_ir_collect_expr_node_literal_tokens(item, out)
      }
    _ => ()
  }
}

///|
fn WgslIrLowerer::lower_runtime_source_local_const_initializer(
  self : WgslIrLowerer,
  expr_ref : @ast.WgslExprRef,
  explicit_ty : Handle?,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let key = wgsl_ir_source_local_const_initializer_key(expr_ref, explicit_ty)
  match scope.runtime_source_local_const_initializer_expressions.get(key) {
    Some(handle) => handle
    None => {
      let handle = self.lower_function_expression_ref_with_expected(
        expr_ref, function, scope, explicit_ty,
      )
      scope.runtime_source_local_const_initializer_expressions.set(key, handle)
      handle
    }
  }
}

///|
fn wgsl_ir_source_local_const_initializer_key(
  expr_ref : @ast.WgslExprRef,
  explicit_ty : Handle?,
) -> String {
  wgsl_ir_source_local_const_token_text_key(
    wgsl_ir_expr_ref_token_text(expr_ref),
    explicit_ty,
  )
}

///|
fn wgsl_ir_source_local_const_literal_key(
  token : @ast.WgslExprToken,
  explicit_ty : Handle?,
) -> String {
  wgsl_ir_source_local_const_token_text_key(token.text(), explicit_ty)
}

///|
fn wgsl_ir_source_local_const_token_text_key(
  token_text : String,
  explicit_ty : Handle?,
) -> String {
  let ty_key = match explicit_ty {
    Some(ty) => "\{ty.index()}"
    None => "-"
  }
  "\{ty_key}:\{token_text}"
}

///|
fn WgslIrLowerer::source_local_const_initializer_semantic_key(
  self : WgslIrLowerer,
  expr_ref : @ast.WgslExprRef,
  expected_ty : Handle?,
) -> String? raise WgslIrLowerError {
  guard expr_ref.root() is Some(root) else { return None }
  self.source_local_const_initializer_node_semantic_key(root, expected_ty)
}

///|
fn WgslIrLowerer::source_local_const_initializer_node_semantic_key(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  expected_ty : Handle?,
) -> String? raise WgslIrLowerError {
  match node.kind() {
    Literal(token) =>
      self.source_local_const_literal_semantic_key(token, expected_ty)
    Call(callee, arguments) =>
      self.source_local_const_vector_splat_semantic_key(
        callee,
        wgsl_ir_call_arguments(arguments),
        expected_ty,
      )
    Group(inner) =>
      self.source_local_const_initializer_node_semantic_key(inner, expected_ty)
    Sequence([single]) =>
      self.source_local_const_initializer_node_semantic_key(single, expected_ty)
    _ => None
  }
}

///|
fn WgslIrLowerer::source_local_const_vector_splat_semantic_key(
  self : WgslIrLowerer,
  callee : @ast.WgslExprNode,
  arguments : Array[@ast.WgslExprNode],
  expected_ty : Handle?,
) -> String? raise WgslIrLowerError {
  guard expected_ty is Some(ty) else { return None }
  guard arguments is [argument] else { return None }
  guard wgsl_ir_expr_node_vector_constructor_name(callee) is Some(name) else {
    return None
  }
  guard wgsl_ir_vector_size_from_constructor_name(name) is Some(_) else {
    return None
  }
  guard argument.kind() is Literal(token) else { return None }
  let component_ty = match self.constructor_component_expected_type(ty) {
    Some(value) => value
    None => ty
  }
  match
    self.source_local_const_literal_semantic_key(token, Some(component_ty)) {
    Some(literal_key) => Some("semantic:splat:\{ty.index()}:\{literal_key}")
    None => None
  }
}

///|
fn wgsl_ir_expr_node_vector_constructor_name(
  node : @ast.WgslExprNode,
) -> String? {
  match node.kind() {
    Identifier(name) => Some(name.name())
    Template(base, _) => wgsl_ir_expr_node_vector_constructor_name(base)
    Group(inner) => wgsl_ir_expr_node_vector_constructor_name(inner)
    _ => None
  }
}

///|
fn WgslIrLowerer::source_local_const_literal_semantic_key(
  self : WgslIrLowerer,
  token : @ast.WgslExprToken,
  expected_ty : Handle?,
) -> String? raise WgslIrLowerError {
  let literal_ty = match expected_ty {
    Some(ty) =>
      match self.constructor_component_expected_type(ty) {
        Some(component_ty) => Some(component_ty)
        None => Some(ty)
      }
    None => None
  }
  let literal = self.literal_from_token_with_expected(token, literal_ty)
  Some(
    "semantic:literal:\{wgsl_ir_source_local_const_literal_value_key(literal_ty, literal)}",
  )
}

///|
fn wgsl_ir_source_local_const_literal_value_key(
  ty : Handle?,
  literal : Literal,
) -> String {
  let ty_key = match ty {
    Some(handle) => "\{handle.index()}"
    None => "-"
  }
  let value_key = match literal {
    Bool(value) => "bool:\{value}"
    F64(value) | AbstractFloat(value) | F16(value) => "float:\{value}"
    F32(value) | F32Exact(value) => "float:\{value.to_double()}"
    I16(value) | I32(value) => "int:\{value}"
    I64(value) | AbstractInt(value) => "int:\{value}"
    U16(value) => "uint:\{value}"
    U32(value) => "uint:\{value}"
    U64(value) => "uint:\{value}"
  }
  "\{ty_key}:\{value_key}"
}