///|
pub fn Diagnostic::line(self : Diagnostic, source : @source.SourceFile) -> Int {
  self.at_line_col(source).0
}

///|
pub fn Diagnostic::column(
  self : Diagnostic,
  source : @source.SourceFile,
) -> Int {
  self.at_line_col(source).1
}

///|
pub fn Diagnostic::location(
  self : Diagnostic,
  source : @source.SourceFile,
) -> String {
  let (line, column) = self.at_line_col(source)
  (if self.file.is_empty() { source.name() } else { self.file }) +
  ":" +
  (line + 1).to_string() +
  ":" +
  (column + 1).to_string()
}

///|
pub fn Diagnostic::source_text(
  self : Diagnostic,
  source : @source.SourceFile,
) -> String {
  source.slice(self.span)
}

///|
pub fn Diagnostic::is_zero_width(self : Diagnostic) -> Bool {
  self.span.is_empty()
}

///|
pub fn Diagnostic::overlaps(self : Diagnostic, span : @source.Span) -> Bool {
  self.span.start < span.end && span.start < self.span.end
}

///|
pub fn diagnostics_overlapping(
  diagnostics : Array[Diagnostic],
  span : @source.Span,
) -> Array[Diagnostic] {
  diagnostics.filter(fn(diagnostic) { diagnostic.overlaps(span) })
}

///|
pub fn diagnostics_at_line(
  diagnostics : Array[Diagnostic],
  source : @source.SourceFile,
  line : Int,
) -> Array[Diagnostic] {
  diagnostics.filter(fn(diagnostic) { diagnostic.line(source) == line })
}

///|
pub fn diagnostics_location_summary(
  diagnostics : Array[Diagnostic],
  source : @source.SourceFile,
) -> String {
  let result : Array[String] = []
  for diagnostic in stable_diagnostic_order(diagnostics) {
    result.push(diagnostic.location(source) + " " + diagnostic.code)
  }
  result.join("; ")
}