///|
pub fn wgsl_ir_block_ends_with_return(block : Block) -> Bool {
  match block.statements {
    [.., Return(_)] | [.., ImplicitReturn] => true
    _ => false
  }
}

///|
fn wgsl_ir_block_accepts_implicit_tail_return(block : Block) -> Bool {
  if wgsl_ir_block_ends_with_return(block) {
    true
  } else {
    block.statements.push(ImplicitReturn)
    true
  }
}

///|
fn wgsl_ir_absorb_implicit_tail_return(block : Block) -> Bool {
  match block.statements {
    [.., If(_, accept, reject)] => {
      let accept_absorbed = wgsl_ir_block_accepts_implicit_tail_return(accept)
      let reject_absorbed = wgsl_ir_block_accepts_implicit_tail_return(reject)
      accept_absorbed && reject_absorbed
    }
    [.., Switch(_, cases)] => {
      for case in cases {
        ignore(wgsl_ir_block_accepts_implicit_tail_return(case.body))
      }
      true
    }
    _ => false
  }
}

///|
fn WgslIrLowerer::lower_block(
  self : WgslIrLowerer,
  block : @ast.WgslBlock,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Block raise WgslIrLowerError {
  let ir_block = Block::Block()
  let statements = block.statements()
  for index in 0.. Block raise WgslIrLowerError {
  let child_scope = scope.child()
  let lowered = self.lower_block(block, function, child_scope)
  lowered
}

///|
fn WgslIrLowerer::lower_statement(
  self : WgslIrLowerer,
  statement : @ast.WgslStatement,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  self.lower_statement_with_local_constant_placement(
    statement,
    function,
    scope,
    block,
    UseSiteLazyLocalConstant,
    [],
  )
}

///|
fn WgslIrLowerer::lower_statement_with_block_context(
  self : WgslIrLowerer,
  statement : @ast.WgslStatement,
  statements : Array[@ast.WgslStatement],
  statement_index : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  if wgsl_ir_statement_is_source_var_declaration(statement) {
    self.predeclare_source_var_suffix_locals(
      statements, statement_index, function, scope,
    )
  }
  let placement = match statement.payload() {
    LocalDeclaration(_) =>
      wgsl_ir_local_constant_arena_placement_for_statement(
        statement, statements, statement_index, scope,
      )
    _ => UseSiteLazyLocalConstant
  }
  let source_var_store_initializers = match statement.payload() {
    LocalDeclaration(locals) =>
      wgsl_ir_source_var_store_initializers_for_statement(
        locals, statements, statement_index, self, scope,
      )
    _ => []
  }
  self.lower_statement_with_local_constant_placement(
    statement, function, scope, block, placement, source_var_store_initializers,
  )
}

///|
fn WgslIrLowerer::predeclare_source_var_suffix_locals(
  self : WgslIrLowerer,
  statements : Array[@ast.WgslStatement],
  statement_index : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  for index in statement_index.. Bool {
  statement.local_bindings().any(fn(local_decl) { local_decl.kind() == Var })
}

///|
fn WgslIrLowerer::lower_statement_with_local_constant_placement(
  self : WgslIrLowerer,
  statement : @ast.WgslStatement,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  local_constant_placement : WgslIrLocalConstantArenaPlacement,
  source_var_store_initializers : Array[String],
) -> Unit raise WgslIrLowerError {
  match statement.payload() {
    Empty => ()
    Compound =>
      match statement.blocks() {
        [child, ..] =>
          block.push(Block(self.lower_child_block(child, function, scope)))
        [] => ()
      }
    LocalDeclaration(locals) =>
      for local_decl in locals {
        self.lower_local_declaration(
          local_decl, function, scope, block, local_constant_placement, source_var_store_initializers,
        )
      }
    Return(expr) => {
      let value = match expr {
        Some(expr_ref) => {
          let expression_mark = function.expressions.items.length()
          let expected_ty = match function.result {
            Some(result) => Some(result.ty)
            None => None
          }
          guard expr_ref.root() is Some(_) else {
            raise UnsupportedExpression("empty return expression")
          }
          let handle = match expr_ref.root() {
            Some(root) if wgsl_ir_expr_node_literal_token(root) is Some(token) =>
              self.lower_function_fresh_literal_node_with_expected(
                token, function, expected_ty,
              )
            Some(root) if wgsl_ir_expr_node_is_short_circuit_binary(root) =>
              self
              .short_circuit_planner(function, scope, block)
              .materialize_return_value_node(root)
            _ =>
              self.lower_expression_ref_with_materialized_short_circuit_inputs(
                expr_ref, function, scope, block, expected_ty,
              )
          }
          let handle = self.materialize_return_expression_if_needed(
            function, scope, block, handle,
          )
          if wgsl_ir_return_expression_should_emit_before_return(
              function, expression_mark, handle,
            ) {
            BodyEmissionPlanner::push_named_expression_emit_ranges(
              block, function, expression_mark, handle,
            )
          } else if !BodyEmissionPlanner::block_contains_expression_emit(
              block, handle,
            ) &&
            !wgsl_ir_function_expression_is_emit_leaf(function, handle) {
            block.push(Emit(HandleRange(handle, handle)))
          }
          Some(handle)
        }
        None => None
      }
      block.push(Return(value))
    }
    If(condition) => {
      guard condition is Some(condition_ref) else {
        raise UnsupportedStatement("if without condition")
      }
      let expression_mark = function.expressions.items.length()
      let condition_handle = self.lower_control_flow_condition_ref(
        condition_ref, function, scope, block,
      )
      let condition_refs = wgsl_ir_statement_condition_refs(
        statement.expressions(),
      )
      guard condition_refs.length() > 0 else {
        raise UnsupportedStatement("if without condition")
      }
      let blocks = statement.blocks()
      let then_block = match statement.blocks() {
        [then_child, ..] => self.lower_child_block(then_child, function, scope)
        [] => Block()
      }
      let else_block = self.lower_if_reject_chain(
        blocks, condition_refs, 1, function, scope,
      )
      let condition_emitted_by_short_circuit = match condition_ref.root() {
        Some(root) => wgsl_ir_expr_node_is_short_circuit_binary(root)
        None => false
      }
      if !condition_emitted_by_short_circuit {
        BodyEmissionPlanner::push_statement_emit_ranges(
          block, function, expression_mark, condition_handle,
        )
      }
      block.push(If(condition_handle, then_block, else_block))
    }
    While(condition) => {
      guard condition is Some(condition_ref) else {
        raise UnsupportedStatement("while without condition")
      }
      let body_statements : Array[Statement] = []
      let guard_block = Block::{ statements: body_statements, }
      let cond = self.lower_control_flow_condition_ref(
        condition_ref, function, scope, guard_block,
      )
      body_statements.push(If(cond, Block(), { statements: [Break], }))
      let lowered_body = match statement.blocks() {
        [body_block, ..] => self.lower_child_block(body_block, function, scope)
        [] => Block()
      }
      if lowered_body.statements.length() > 0 {
        body_statements.push(Block(lowered_body))
      }
      block.push(Loop(guard_block, Block(), None))
    }
    Loop => {
      let (body, continuing, break_if) = self.lower_loop_blocks(
        statement, function, scope,
      )
      block.push(Loop(body, continuing, break_if))
    }
    Break(_) => block.push(Break)
    Continue => block.push(Continue)
    Discard => block.push(Kill)
    Assignment(op, lhs, rhs) => {
      guard lhs is Some(lhs_ref) else {
        raise UnsupportedStatement("assignment without lhs")
      }
      guard rhs is Some(rhs_ref) else {
        raise UnsupportedStatement("assignment without rhs")
      }
      let expression_mark = function.expressions.items.length()
      if wgsl_ir_expr_ref_is_phony(lhs_ref) {
        if self.lower_ray_query_statement_expr_ref(
            rhs_ref, function, scope, block,
          ) {
          return
        }
        if self.wgsl_ir_expr_ref_is_user_function_call(rhs_ref) {
          self.lower_call_statement(rhs_ref, function, scope, block)
          return
        }
        let rhs_value = self.lower_expression_ref_with_materialized_short_circuit_inputs(
          rhs_ref,
          function,
          scope,
          block,
          None,
        )
        block.push(Phony(rhs_value))
        return
      }
      let (target, value) = match wgsl_ir_compound_assignment_operator(op) {
        Some(binary_op) => {
          scope.pending_compound_assignment_rhs_local_root = match
            rhs_ref.root() {
            Some(root) =>
              wgsl_ir_expr_node_plain_mutable_local_identifier_name(root, scope)
            None => None
          }
          match rhs_ref.root() {
            Some(root) =>
              if wgsl_ir_direct_access_chain_root_is_function_argument(
                  root, scope,
                ) {
                self.predeclare_function_reference_roots_in_source_order(
                  root, function, scope,
                )
              }
            None => ()
          }
          let raw_target = self.lower_function_lvalue_ref(
            lhs_ref, function, scope,
          )
          scope.pending_compound_assignment_rhs_local_root = None
          let target = self.materialize_store_pointer_if_needed(
            function, scope, block, raw_target,
          )
          if wgsl_ir_expr_ref_plain_local_identifier(lhs_ref, scope) {
            match rhs_ref.root() {
              Some(root) =>
                if wgsl_ir_compound_assignment_rhs_predeclares_reference_roots(
                    root, scope,
                  ) {
                  self.predeclare_function_binary_chain_reference_roots_in_source_order(
                    root, function, scope,
                  )
                }
              None => ()
            }
          }
          match rhs_ref.root() {
            Some(root) =>
              if wgsl_ir_direct_access_chain_root_is_function_argument(
                  root, scope,
                ) {
                self.predeclare_function_reference_roots_in_source_order(
                  root, function, scope,
                )
              }
            None => ()
          }
          let target_value = self.compound_assignment_target_value(
            function, target,
          )
          let materialized_target = self.materialize_function_expression(
            function, scope, block, target_value,
          )
          scope.compound_assignment_rhs_depth += 1
          let rhs_value = self.lower_expression_ref_with_materialized_short_circuit_inputs(
            rhs_ref,
            function,
            scope,
            block,
            None,
          )
          scope.compound_assignment_rhs_depth -= 1
          let rhs_value = self.materialize_store_value_if_needed(
            function, scope, block, rhs_value,
          )
          let (materialized_target, rhs_value) = self.normalize_function_binary_operands(
            function, binary_op, materialized_target, rhs_value,
          )
          let value = function.expressions.append(
            Binary(binary_op, materialized_target, rhs_value),
          )
          (target, value)
        }
        None =>
          if wgsl_ir_expr_ref_plain_local_identifier(lhs_ref, scope) {
            let early_raw_target : Handle? = if wgsl_ir_expr_ref_root_is_literal_like(
                rhs_ref,
              ) {
              Some(self.lower_function_lvalue_ref(lhs_ref, function, scope))
            } else {
              None
            }
            let rhs_value = self.lower_expression_ref_with_materialized_short_circuit_inputs(
              rhs_ref,
              function,
              scope,
              block,
              None,
            )
            let raw_target = match early_raw_target {
              Some(target) => target
              None => self.lower_function_lvalue_ref(lhs_ref, function, scope)
            }
            let target = self.materialize_store_pointer_if_needed(
              function, scope, block, raw_target,
            )
            let value = self.materialize_store_value_for_pointer_if_needed(
              function, scope, block, target, rhs_value,
            )
            (target, value)
          } else if self.wgsl_ir_expr_ref_is_function_argument_pointer_deref(
              lhs_ref, function, scope,
            ) {
            let store_pointer_root = self.function_argument_pointer_deref_name_ref(
              lhs_ref, function, scope,
            )
            if store_pointer_root is Some(name) &&
              self.rhs_user_call_reads_pointer_argument(rhs_ref, name) {
              ignore(
                scope.argument_pointer_expression(
                  name,
                  scope.arguments[name],
                  function,
                ),
              )
            }
            let previous_pending = scope.pending_store_pointer_argument_root
            scope.pending_store_pointer_argument_root = store_pointer_root
            let rhs_value = self.lower_expression_ref_with_materialized_short_circuit_inputs(
              rhs_ref,
              function,
              scope,
              block,
              None,
            )
            scope.pending_store_pointer_argument_root = previous_pending
            let raw_target = self.lower_function_lvalue_ref(
              lhs_ref, function, scope,
            )
            let target = self.materialize_store_pointer_if_needed(
              function, scope, block, raw_target,
            )
            let value = self.materialize_store_value_for_pointer_if_needed(
              function, scope, block, target, rhs_value,
            )
            (target, value)
          } else {
            predeclare_assignment_rhs_root_before_target_if_needed(
              rhs_ref, function, scope,
            )
            let raw_target = self.lower_function_lvalue_ref(
              lhs_ref, function, scope,
            )
            let target = self.materialize_store_pointer_if_needed(
              function, scope, block, raw_target,
            )
            let rhs_value = self.lower_expression_ref_with_materialized_short_circuit_inputs(
              rhs_ref,
              function,
              scope,
              block,
              None,
            )
            let value = self.materialize_store_value_for_pointer_if_needed(
              function, scope, block, target, rhs_value,
            )
            (target, value)
          }
      }
      wgsl_ir_record_store_value_temporary_references(function, value)
      BodyEmissionPlanner::push_statement_emit_ranges(
        block,
        function,
        expression_mark,
        wgsl_ir_max_handle(target, value),
      )
      block.push(Store(target, value))
    }
    Increment(target) =>
      self.lower_update_statement(target, function, scope, block, true)
    Decrement(target) =>
      self.lower_update_statement(target, function, scope, block, false)
    FunctionCall(expr) => {
      guard expr is Some(expr_ref) else {
        raise UnsupportedStatement("function call without expression")
      }
      self.lower_call_statement(expr_ref, function, scope, block)
    }
    Expression(expr) =>
      match expr {
        Some(expr_ref) =>
          if self.lower_ray_query_statement_expr_ref(
              expr_ref, function, scope, block,
            ) {
            ()
          } else if wgsl_ir_expr_ref_is_call(expr_ref) {
            self.lower_call_statement(expr_ref, function, scope, block)
          } else {
            let handle = self.lower_function_expression_ref(
              expr_ref, function, scope,
            )
            block.push(Emit(HandleRange(handle, handle)))
          }
        None => ()
      }
    ConstAssert(expr) => {
      guard expr is Some(expr_ref) else {
        raise UnsupportedStatement("const_assert without expression")
      }
      block.push(
        ConstAssert(
          self.lower_function_expression_ref(expr_ref, function, scope),
        ),
      )
    }
    For(expressions) =>
      self.lower_for_statement(statement, expressions, function, scope, block)
    Switch(selector) => {
      guard selector is Some(selector_ref) else {
        raise UnsupportedStatement("switch without selector")
      }
      let expression_mark = function.expressions.items.length()
      let lowered_selector = self.lower_function_expression_ref(
        selector_ref, function, scope,
      )
      let selector_handle = self.materialize_control_flow_condition_if_needed(
        function, scope, block, lowered_selector,
      )
      let cases : Array[SwitchCase] = []
      match statement.blocks() {
        [switch_block, ..] =>
          self.lower_switch_cases(
            switch_block, function, scope, selector_handle, cases,
          )
        [] => ()
      }
      BodyEmissionPlanner::push_statement_emit_ranges(
        block, function, expression_mark, selector_handle,
      )
      block.push(Switch(selector_handle, cases))
    }
    Case(_) | DefaultCase =>
      raise UnsupportedStatement(
        "structured statement not yet representable by this WGSL IR lowerer",
      )
  }
}

///|
fn WgslIrLowerer::lower_local_declaration(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  local_constant_placement : WgslIrLocalConstantArenaPlacement,
  source_var_store_initializers : Array[String],
) -> Unit raise WgslIrLowerError {
  if local_decl.kind() == Const {
    self.lower_local_const_declaration(
      local_decl, function, scope, local_constant_placement,
    )
    return
  }
  if local_decl.kind() == Var &&
    source_var_store_initializers.any(fn(name) {
      name == local_decl.name().name()
    }) {
    self.lower_local_var_store_declaration(local_decl, function, scope, block)
    return
  }
  if local_decl.kind() == Var &&
    !scope.predeclared_source_locals.contains(
      wgsl_ir_local_declaration_key(local_decl),
    ) &&
    !self.local_var_initializer_uses_store(local_decl) {
    self.predeclare_source_local_placeholder(local_decl, function, scope)
  }
  if local_decl.kind() == Var &&
    scope.predeclared_source_locals.contains(
      wgsl_ir_local_declaration_key(local_decl),
    ) {
    if wgsl_ir_predeclared_source_var_has_init(local_decl, function, scope) {
      ignore(
        wgsl_ir_lower_predeclared_source_var_declaration(
          local_decl, scope, block,
        ),
      )
    } else {
      match local_decl.initializer_ref() {
        Some(expr_ref) => {
          let initializer_is_const = match expr_ref.root() {
            Some(root) => self.expression_node_is_const_initializer(root)
            None => false
          }
          if initializer_is_const {
            let declaration_placement = match local_constant_placement {
              DeclarationArenaLocalConstant => true
              UseSiteLazyLocalConstant => false
            }
            if scope.loop_depth == 0 || declaration_placement {
              self.lower_predeclared_source_var_const_initializer(
                local_decl, expr_ref, function, scope, block,
              )
            } else {
              self.lower_local_var_store_declaration(
                local_decl, function, scope, block,
              )
            }
          } else {
            self.lower_local_var_store_declaration(
              local_decl, function, scope, block,
            )
          }
        }
        None =>
          ignore(
            wgsl_ir_lower_predeclared_source_var_declaration(
              local_decl, scope, block,
            ),
          )
      }
    }
    return
  }
  if local_decl.kind() == Var &&
    self.local_var_initializer_uses_store(local_decl) {
    self.lower_local_var_store_declaration(local_decl, function, scope, block)
    return
  }
  if local_decl.kind() == Var &&
    wgsl_ir_lower_predeclared_source_var_declaration(local_decl, scope, block) {
    return
  }
  let expression_mark = function.expressions.items.length()
  let local_constant_snapshot = wgsl_ir_snapshot_local_constant_values(
    scope.local_constants,
  )
  let explicit_ty = match local_decl.ty_ref() {
    Some(ty_ref) => Some(self.lower_function_type_ref(ty_ref, function, scope))
    None => None
  }
  let mut address_of_ty : Handle? = None
  let mut init = match local_decl.initializer_ref() {
    Some(expr_ref) =>
      match wgsl_ir_address_of_operand_ref(expr_ref) {
        Some(target_ref) => {
          let target = self.lower_function_lvalue_node(
            target_ref, function, scope,
          )
          let pointer_ty = self.pointer_type_for_lvalue(function, target)
          address_of_ty = Some(pointer_ty)
          if local_decl.kind() == Let {
            Some(target)
          } else {
            Some(function.expressions.append(AddressOf(pointer_ty, target)))
          }
        }
        None => {
          let init_is_const = if local_decl.kind() == Var {
            match expr_ref.root() {
              Some(root) => self.expression_node_is_const_initializer(root)
              None => false
            }
          } else {
            false
          }
          Some(
            if init_is_const {
              self.lower_runtime_source_local_const_initializer(
                expr_ref, explicit_ty, function, scope,
              )
            } else if local_decl.kind() == Let &&
              wgsl_ir_expr_ref_references_local_let_constant(expr_ref, scope) {
              scope.source_let_initializer_preserve_depth += 1
              let handle = self.lower_function_expression_ref_with_expected(
                expr_ref, function, scope, explicit_ty,
              )
              scope.source_let_initializer_preserve_depth -= 1
              handle
            } else {
              self.lower_function_expression_ref_with_expected(
                expr_ref, function, scope, explicit_ty,
              )
            },
          )
        }
      }
    None => None
  }
  if local_decl.kind() == Let {
    match init {
      Some(init_handle) => {
        if self.local_let_const_scalar_initializer_should_fold(
            local_decl, init_handle, function, scope,
          ) {
          let normalized = self.normalized_local_constant_initializer(
            function, init_handle, explicit_ty,
          )
          function.expressions.set_origin(normalized, FoldedLocalConstant)
          ignore(scope.locals.remove(local_decl.name().name()))
          ignore(
            scope.local_pointer_expressions.remove(local_decl.name().name()),
          )
          match local_constant_placement {
            DeclarationArenaLocalConstant =>
              scope.local_constants.set(
                local_decl.name().name(),
                wgsl_ir_local_let_value_cell(ExpressionAlias(normalized, None)),
              )
            UseSiteLazyLocalConstant => {
              let normalized_expression = wgsl_ir_lazy_local_constant_expression(
                function, normalized,
              )
              wgsl_ir_rewind_function_expressions_and_local_constant_values(
                function, expression_mark, scope, local_constant_snapshot,
              )
              match local_decl.initializer_ref() {
                Some(expr_ref) =>
                  scope.local_constants.set(
                    local_decl.name().name(),
                    wgsl_ir_local_let_value_cell(
                      LazyConstant({
                        expr_ref,
                        explicit_ty,
                        normalized: normalized_expression,
                      }),
                    ),
                  )
                None => ()
              }
            }
          }
          return
        }
        let materialized = self.materialize_return_subexpressions(
          function, scope, block, init_handle,
        )
        if materialized != init_handle {
          init = Some(materialized)
        }
        match init {
          Some(normalized) =>
            if self.function_expression_should_materialize_as_let_initializer(
                function, normalized,
              ) {
              let materialized_value = self.materialize_function_expression(
                function, scope, block, normalized,
              )
              ignore(scope.locals.remove(local_decl.name().name()))
              ignore(
                scope.local_pointer_expressions.remove(local_decl.name().name()),
              )
              scope.local_constants.set(
                local_decl.name().name(),
                wgsl_ir_local_let_value_cell(
                  ExpressionAlias(materialized_value, None),
                ),
              )
              return
            } else if wgsl_ir_function_expression_is_materialized_temporary(
                function, normalized,
              ) {
              ignore(scope.locals.remove(local_decl.name().name()))
              ignore(
                scope.local_pointer_expressions.remove(local_decl.name().name()),
              )
              scope.local_constants.set(
                local_decl.name().name(),
                wgsl_ir_local_let_value_cell(ExpressionAlias(normalized, None)),
              )
              return
            }
          _ => ()
        }
      }
      None => ()
    }
  }
  let ty = match explicit_ty {
    Some(ty) => ty
    None =>
      match address_of_ty {
        Some(pointer_ty) => pointer_ty
        None =>
          match init {
            Some(init_handle) =>
              match self.address_of_expression_type(function, init_handle) {
                Some(inferred) => inferred
                None =>
                  match self.function_expression_type(function, init_handle) {
                    Some(inferred) => inferred
                    None =>
                      self.infer_local_type_from_initializer(
                        local_decl.name().name(),
                        local_decl.initializer_ref(),
                      )
                  }
              }
            None =>
              self.infer_local_type_from_initializer(
                local_decl.name().name(),
                local_decl.initializer_ref(),
              )
          }
      }
  }
  let ty = self.local_declaration_storage_type(local_decl.kind(), ty)
  match init {
    Some(init_handle) =>
      self.contextualize_function_expression_for_type(function, init_handle, ty)
    None => ()
  }
  if local_decl.kind() == Var {
    match local_decl.initializer_ref() {
      Some(expr_ref) =>
        match expr_ref.root() {
          Some(root) =>
            if self.expression_node_is_const_initializer(root) {
              self.record_source_local_const_initializer_literals(
                expr_ref,
                expression_mark,
                function,
                scope,
                Some(ty),
              )
            }
          None => ()
        }
      None => ()
    }
  }
  if local_decl.kind() == Let {
    match init {
      Some(init_handle) =>
        if self.function_expression_is_handle_resource_value(
            function, init_handle,
          ) {
          ignore(scope.locals.remove(local_decl.name().name()))
          ignore(
            scope.local_pointer_expressions.remove(local_decl.name().name()),
          )
          scope.local_constants.set(
            local_decl.name().name(),
            wgsl_ir_local_let_value_cell(ExpressionAlias(init_handle, None)),
          )
          return
        }
      None => ()
    }
  }
  let lowered_name = local_decl.name().name()
  if local_decl.kind() == Let {
    match init {
      Some(init_handle) =>
        if wgsl_ir_function_expression_should_use_named_let(
            function, init_handle,
          ) {
          if wgsl_ir_function_replace_named_expression(
              function, init_handle, lowered_name, ty,
            ) {
            ignore(scope.locals.remove(local_decl.name().name()))
            ignore(
              scope.local_pointer_expressions.remove(local_decl.name().name()),
            )
            scope.local_constants.set(
              local_decl.name().name(),
              wgsl_ir_local_let_value_cell(ExpressionAlias(init_handle, None)),
            )
            return
          }
          function.named_expressions.push({
            handle: init_handle,
            name: lowered_name,
            ty,
          })
          ignore(scope.locals.remove(local_decl.name().name()))
          ignore(
            scope.local_pointer_expressions.remove(local_decl.name().name()),
          )
          scope.local_constants.set(
            local_decl.name().name(),
            wgsl_ir_local_let_value_cell(ExpressionAlias(init_handle, None)),
          )
          BodyEmissionPlanner::push_named_expression_emit_ranges(
            block, function, expression_mark, init_handle,
          )
          return
        }
      None => ()
    }
  }
  let var_init_in_declaration = if local_decl.kind() == Var {
    match init {
      Some(init_handle) =>
        self.function_expression_is_const_expression(function, init_handle)
      None => false
    }
  } else {
    false
  }
  let handle = function.local_variables.append({
    name: Some(lowered_name),
    kind: wgsl_ir_local_variable_kind(local_decl.kind()),
    generated_temporary: false,
    generated_temporary_store_value_reference: false,
    short_circuit_result: false,
    ty,
    init: if local_decl.kind() == Var {
      if var_init_in_declaration {
        init
      } else {
        None
      }
    } else {
      init
    },
  })
  ignore(scope.local_constants.remove(local_decl.name().name()))
  ignore(scope.local_pointer_expressions.remove(local_decl.name().name()))
  scope.locals.set(local_decl.name().name(), handle)
  block.push(Declare(handle))
  if local_decl.kind() == Var {
    match init {
      Some(init_handle) =>
        if !var_init_in_declaration {
          let materialized = self.materialize_store_value_if_needed(
            function, scope, block, init_handle,
          )
          wgsl_ir_record_store_value_temporary_references(
            function, materialized,
          )
          BodyEmissionPlanner::push_statement_emit_ranges(
            block, function, expression_mark, materialized,
          )
          block.push(
            Store(
              scope.local_pointer_expression(
                local_decl.name().name(),
                handle,
                function,
              ),
              materialized,
            ),
          )
        }
      _ => ()
    }
  }
}

///|
fn wgsl_ir_predeclared_source_var_has_init(
  local_decl : @ast.WgslLocalDeclaration,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  match
    scope.predeclared_source_locals.get(
      wgsl_ir_local_declaration_key(local_decl),
    ) {
    Some(handle) =>
      match function.local_variables.items.get(handle.index()) {
        Some(local_var) => local_var.init != None
        None => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::lower_predeclared_source_var_const_initializer(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  expr_ref : @ast.WgslExprRef,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  guard scope.predeclared_source_locals.get(
      wgsl_ir_local_declaration_key(local_decl),
    )
    is Some(handle) else {
    return
  }
  self.lower_predeclared_source_var_const_initializer_to_handle(
    local_decl, expr_ref, function, scope, handle,
  )
  ignore(
    wgsl_ir_lower_predeclared_source_var_declaration(local_decl, scope, block),
  )
}

///|
fn WgslIrLowerer::lower_predeclared_source_var_const_initializer_to_handle(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  expr_ref : @ast.WgslExprRef,
  function : Function,
  scope : WgslIrFunctionScope,
  handle : Handle,
) -> Unit raise WgslIrLowerError {
  let lowered_name = local_decl.name().name()
  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,
  )
  let init = self.lower_source_local_const_initializer(
    expr_ref, expected_ty, function, scope,
  )
  let ty = match explicit_ty {
    Some(ty) => ty
    None =>
      match self.function_expression_type(function, init) {
        Some(inferred) => inferred
        None =>
          self.infer_local_type_from_initializer(
            local_decl.name().name(),
            local_decl.initializer_ref(),
          )
      }
  }
  let ty = self.local_declaration_storage_type(local_decl.kind(), ty)
  self.contextualize_function_expression_for_type(function, init, ty)
  function.local_variables.set(handle, {
    name: Some(lowered_name),
    kind: Var,
    generated_temporary: false,
    generated_temporary_store_value_reference: false,
    short_circuit_result: false,
    ty,
    init: Some(init),
  })
}

///|
fn WgslIrLowerer::local_var_initializer_uses_store(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
) -> Bool raise WgslIrLowerError {
  match local_decl.initializer_ref() {
    Some(expr_ref) =>
      match expr_ref.root() {
        Some(root) =>
          !self.expression_node_is_const_initializer(root) &&
          !wgsl_ir_expression_node_contains_call(root)
        None => false
      }
    None => false
  }
}

///|
fn wgsl_ir_lower_predeclared_source_var_declaration(
  local_decl : @ast.WgslLocalDeclaration,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Bool {
  let key = wgsl_ir_local_declaration_key(local_decl)
  match scope.predeclared_source_locals.get(key) {
    Some(handle) => {
      ignore(scope.local_constants.remove(local_decl.name().name()))
      ignore(scope.local_pointer_expressions.remove(local_decl.name().name()))
      scope.locals.set(local_decl.name().name(), handle)
      block.push(Declare(handle))
      true
    }
    None => false
  }
}

///|
fn WgslIrLowerer::expression_node_is_const_initializer(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
) -> Bool raise WgslIrLowerError {
  match node.kind() {
    Literal(_) => true
    Identifier(name) =>
      self.constant_declarations.contains(name.name()) ||
      self.constant_names.contains(name.name())
    Group(inner) => self.expression_node_is_const_initializer(inner)
    Unary(_, inner) => self.expression_node_is_const_initializer(inner)
    Binary(_, left, right) =>
      self.expression_node_is_const_initializer(left) &&
      self.expression_node_is_const_initializer(right)
    Call(callee, arguments) =>
      self.call_expression_is_const_initializer(callee, arguments)
    Sequence([single]) => self.expression_node_is_const_initializer(single)
    _ => false
  }
}

///|
fn WgslIrLowerer::call_expression_is_const_initializer(
  self : WgslIrLowerer,
  callee : @ast.WgslExprNode,
  arguments : Array[@ast.WgslExprNode],
) -> Bool raise WgslIrLowerError {
  guard self.expression_callee_is_const_constructor(callee) else {
    return false
  }
  let arguments = wgsl_ir_call_arguments(arguments)
  for argument in arguments {
    if !self.expression_node_is_const_initializer(argument) {
      return false
    }
  }
  true
}

///|
fn WgslIrLowerer::expression_callee_is_const_constructor(
  self : WgslIrLowerer,
  callee : @ast.WgslExprNode,
) -> Bool raise WgslIrLowerError {
  match callee.kind() {
    Identifier(ident) =>
      self.type_cache.contains(ident.name()) ||
      self.lower_bitcast_callee_type(callee) != None ||
      self.lower_templated_callee_type(callee) != None ||
      wgsl_ir_untemplated_constructor_name_is_const(ident.name())
    Template(base, _) => self.expression_callee_is_const_constructor(base)
    _ => false
  }
}

///|
fn wgsl_ir_untemplated_constructor_name_is_const(name : String) -> Bool {
  name == "bool" ||
  name == "i32" ||
  name == "u32" ||
  name == "f32" ||
  name == "f16" ||
  name == "vec2" ||
  name == "vec3" ||
  name == "vec4" ||
  name == "mat2x2" ||
  name == "mat2x3" ||
  name == "mat2x4" ||
  name == "mat3x2" ||
  name == "mat3x3" ||
  name == "mat3x4" ||
  name == "mat4x2" ||
  name == "mat4x3" ||
  name == "mat4x4" ||
  name == "array"
}

///|
fn wgsl_ir_expression_node_contains_call(node : @ast.WgslExprNode) -> Bool {
  match node.kind() {
    Call(_, _) => true
    Group(inner) => wgsl_ir_expression_node_contains_call(inner)
    Unary(_, inner) => wgsl_ir_expression_node_contains_call(inner)
    Binary(_, left, right) =>
      wgsl_ir_expression_node_contains_call(left) ||
      wgsl_ir_expression_node_contains_call(right)
    Member(base, _) => wgsl_ir_expression_node_contains_call(base)
    Index(base, index) =>
      wgsl_ir_expression_node_contains_call(base) ||
      wgsl_ir_expression_node_contains_call(index)
    Sequence(items) => {
      for item in items {
        if wgsl_ir_expression_node_contains_call(item) {
          return true
        }
      }
      false
    }
    Template(base, _) => wgsl_ir_expression_node_contains_call(base)
    _ => false
  }
}

///|
fn WgslIrLowerer::lower_local_var_store_declaration(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  let explicit_ty = match local_decl.ty_ref() {
    Some(ty_ref) => Some(self.lower_function_type_ref(ty_ref, function, scope))
    None => None
  }
  let placeholder_ty = match explicit_ty {
    Some(ty) => ty
    None => self.lower_builtin_type_name("i32")
  }
  let placeholder_ty = self.local_declaration_storage_type(
    local_decl.kind(),
    placeholder_ty,
  )
  let lowered_name = local_decl.name().name()
  let handle = match
    scope.predeclared_source_locals.get(
      wgsl_ir_local_declaration_key(local_decl),
    ) {
    Some(predeclared) => predeclared
    None =>
      function.local_variables.append({
        name: Some(lowered_name),
        kind: Var,
        generated_temporary: false,
        generated_temporary_store_value_reference: false,
        short_circuit_result: false,
        ty: placeholder_ty,
        init: None,
      })
  }
  function.local_variables.set(handle, {
    name: Some(lowered_name),
    kind: Var,
    generated_temporary: false,
    generated_temporary_store_value_reference: false,
    short_circuit_result: false,
    ty: placeholder_ty,
    init: None,
  })
  block.push(Declare(handle))
  let predeclare_pointer = self.local_var_store_initializer_predeclares_pointer(
    local_decl, scope,
  )
  let mut pointer : Handle? = if predeclare_pointer {
    Some(function.expressions.append(LocalVariable(handle)))
  } else {
    None
  }
  guard local_decl.initializer_ref() is Some(expr_ref) else { return }
  let expression_mark = function.expressions.items.length()
  let init = match wgsl_ir_address_of_operand_ref(expr_ref) {
    Some(target_ref) => {
      let target = self.lower_function_lvalue_node(target_ref, function, scope)
      function.expressions.append(
        AddressOf(self.pointer_type_for_lvalue(function, target), target),
      )
    }
    None =>
      match expr_ref.root() {
        Some(root) if wgsl_ir_expr_node_literal_token(root) is Some(token) =>
          self.lower_function_fresh_literal_node_with_expected(
            token, function, explicit_ty,
          )
        _ =>
          self.lower_expression_ref_with_materialized_short_circuit_inputs(
            expr_ref, function, scope, block, explicit_ty,
          )
      }
  }
  if pointer == None {
    pointer = Some(function.expressions.append(LocalVariable(handle)))
  }
  let ty = match explicit_ty {
    Some(ty) => ty
    None =>
      match self.address_of_expression_type(function, init) {
        Some(inferred) => inferred
        None =>
          match self.function_expression_type(function, init) {
            Some(inferred) => inferred
            None =>
              self.infer_local_type_from_initializer(
                local_decl.name().name(),
                local_decl.initializer_ref(),
              )
          }
      }
  }
  let ty = self.local_declaration_storage_type(local_decl.kind(), ty)
  function.local_variables.set(handle, {
    name: Some(lowered_name),
    kind: Var,
    generated_temporary: false,
    generated_temporary_store_value_reference: false,
    short_circuit_result: false,
    ty,
    init: None,
  })
  ignore(scope.local_constants.remove(local_decl.name().name()))
  ignore(scope.local_pointer_expressions.remove(local_decl.name().name()))
  scope.locals.set(local_decl.name().name(), handle)
  guard pointer is Some(pointer) else {
    raise UnsupportedStatement(
      "missing local pointer for var \{local_decl.name().name()}",
    )
  }
  scope.local_pointer_expressions.set(local_decl.name().name(), pointer)
  self.contextualize_function_expression_for_type(function, init, ty)
  let materialized = self.materialize_store_value_if_needed(
    function, scope, block, init,
  )
  wgsl_ir_record_store_value_temporary_references(function, materialized)
  BodyEmissionPlanner::push_statement_emit_ranges(
    block, function, expression_mark, materialized,
  )
  block.push(Store(pointer, materialized))
}

///|
fn WgslIrLowerer::local_var_store_initializer_predeclares_pointer(
  self : WgslIrLowerer,
  local_decl : @ast.WgslLocalDeclaration,
  scope : WgslIrFunctionScope,
) -> Bool raise WgslIrLowerError {
  match local_decl.initializer_ref() {
    Some(expr_ref) =>
      match expr_ref.root() {
        Some(root) =>
          self.expression_node_is_const_initializer(root) ||
          wgsl_ir_expr_node_is_direct_function_argument_value(root, scope)
        None => false
      }
    None => false
  }
}

///|
fn wgsl_ir_expr_node_is_direct_function_argument_value(
  node : @ast.WgslExprNode,
  scope : WgslIrFunctionScope,
) -> Bool {
  match node.kind() {
    Group(inner) =>
      wgsl_ir_expr_node_is_direct_function_argument_value(inner, scope)
    Identifier(name) => scope.arguments.contains(name.name())
    _ => false
  }
}