///|
fn WgslIrValidator::validate_type_inner(
  self : WgslIrValidator,
  inner : TypeInner,
) -> Unit raise WgslIrValidationError {
  match inner {
    Pointer(base, space) => {
      self.require_type(base)
      self.validate_pointer_address_space(space)
    }
    ValuePointer(_, _, space) => self.validate_pointer_address_space(space)
    Array(base, size, stride) => {
      self.require_type(base)
      self.validate_array_size(size)
      self.validate_array_stride(base, stride)
    }
    BindingArray(base, size) => {
      self.require_type(base)
      self.validate_array_size(size)
    }
    Struct(members, span) => self.validate_struct_layout(members, span)
    PredeclaredStruct(members, _) =>
      for struct_member in members {
        self.require_type(struct_member.ty)
      }
    Atomic(scalar) => self.validate_atomic_scalar(scalar)
    Matrix(_, _, scalar) => self.validate_matrix_scalar(scalar)
    _ => ()
  }
}

///|
fn WgslIrValidator::validate_array_size(
  self : WgslIrValidator,
  size : ArraySize,
) -> Unit raise WgslIrValidationError {
  match size {
    Pending(handle) => self.validate_global_expression_handle(handle)
    Constant(_) | Dynamic => ()
  }
}

///|
fn WgslIrValidator::validate_global_variable(
  self : WgslIrValidator,
  global : GlobalVariable,
) -> Unit raise WgslIrValidationError {
  self.require_type(global.ty)
  self.validate_global_address_space(global)
  match global.binding {
    Some(binding) => {
      if binding.group < 0 || binding.binding < 0 {
        raise InvalidGlobalVariable("negative resource binding")
      }
      if !self.global_address_space_accepts_resource_binding(global.space) {
        raise InvalidGlobalVariable(
          "resource binding on non-resource address space",
        )
      }
    }
    None => ()
  }
  match global.init {
    Some(init) => {
      if !self.global_address_space_accepts_initializer(global.space) {
        raise InvalidGlobalVariable(
          "initializer on non-initializable address space",
        )
      }
      self.validate_global_expression_handle(init)
    }
    None => ()
  }
}

///|
fn WgslIrValidator::validate_global_address_space(
  self : WgslIrValidator,
  global : GlobalVariable,
) -> Unit raise WgslIrValidationError {
  match global.space {
    Handle =>
      if !self.type_is_handle_resource(global.ty) {
        raise InvalidGlobalVariable(
          "handle address space requires handle resource type",
        )
      }
    Storage(access) =>
      if access == StorageAccess::empty() {
        raise InvalidGlobalVariable("storage address space has empty access")
      }
    _ => ()
  }
}

///|
fn WgslIrValidator::global_address_space_accepts_resource_binding(
  self : WgslIrValidator,
  space : AddressSpace,
) -> Bool {
  ignore(self)
  match space {
    Uniform | Storage(_) | Handle => true
    _ => false
  }
}

///|
fn WgslIrValidator::global_address_space_accepts_initializer(
  self : WgslIrValidator,
  space : AddressSpace,
) -> Bool {
  ignore(self)
  match space {
    Private => true
    _ => false
  }
}

///|
fn WgslIrValidator::validate_pointer_address_space(
  self : WgslIrValidator,
  space : AddressSpace,
) -> Unit raise WgslIrValidationError {
  ignore(self)
  match space {
    Function
    | Private
    | WorkGroup
    | Uniform
    | Storage(_)
    | TaskPayload
    | RayPayload
    | IncomingRayPayload => ()
    Handle | Immediate =>
      raise InvalidType("pointer uses non-addressable address space")
  }
}

///|
fn WgslIrValidator::validate_atomic_scalar(
  self : WgslIrValidator,
  scalar : Scalar,
) -> Unit raise WgslIrValidationError {
  ignore(self)
  if !(scalar.kind == Sint || scalar.kind == Uint) || scalar.width != 4 {
    raise InvalidType("atomic type must use i32 or u32 scalar")
  }
}

///|
fn WgslIrValidator::validate_matrix_scalar(
  self : WgslIrValidator,
  scalar : Scalar,
) -> Unit raise WgslIrValidationError {
  ignore(self)
  if scalar.kind != Float && scalar.kind != AbstractFloat {
    raise InvalidType("matrix type must use a floating-point scalar")
  }
}

///|
fn WgslIrValidator::validate_array_stride(
  self : WgslIrValidator,
  base : Handle,
  stride : Int32,
) -> Unit raise WgslIrValidationError {
  if stride <= 0 {
    raise InvalidType("array stride must be positive")
  }
  let layout = self.type_layout(base)
  if stride < layout.size {
    let element_text = match self.shader_module.types.items.get(base.index()) {
      Some(value) => @debug.to_string(value)
      None => ""
    }
    raise InvalidType(
      "array stride is smaller than element size: stride=\{stride}, element_size=\{layout.size}, element_type=\{base.index()} \{element_text}",
    )
  }
  if stride % layout.align != 0 {
    let element_text = match self.shader_module.types.items.get(base.index()) {
      Some(value) => @debug.to_string(value)
      None => ""
    }
    raise InvalidType(
      "array stride is not aligned to element alignment: stride=\{stride}, element_align=\{layout.align}, element_type=\{base.index()} \{element_text}",
    )
  }
}

///|
fn WgslIrValidator::validate_struct_layout(
  self : WgslIrValidator,
  members : Array[StructMember],
  span : Int32,
) -> Unit raise WgslIrValidationError {
  if span <= 0 {
    raise InvalidType("struct span must be positive")
  }
  let mut end = 0
  let mut max_align = 1
  for index in 0.. value
      None => layout.align
    }
    if !wgsl_ir_validation_is_power_of_two(member_align) ||
      member_align < layout.align {
      raise InvalidType("struct member alignment is invalid")
    }
    let member_size = match struct_member.size {
      Some(value) => value
      None => layout.size
    }
    if member_size < layout.size {
      raise InvalidType("struct member size is smaller than type size")
    }
    if struct_member.offset < end {
      raise InvalidType("struct member offset overlaps previous member")
    }
    if struct_member.offset % member_align != 0 {
      raise InvalidType("struct member offset is not aligned")
    }
    end = struct_member.offset + member_size
    if member_align > max_align {
      max_align = member_align
    }
  }
  if span < end {
    raise InvalidType("struct span is smaller than member range")
  }
  if span % max_align != 0 {
    raise InvalidType("struct span is not aligned to struct alignment")
  }
}