summaryrefslogtreecommitdiffstats
path: root/vendor/rustfix/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/rustfix/src
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/rustfix/src')
-rw-r--r--vendor/rustfix/src/diagnostics.rs89
-rw-r--r--vendor/rustfix/src/lib.rs263
-rw-r--r--vendor/rustfix/src/replace.rs323
3 files changed, 675 insertions, 0 deletions
diff --git a/vendor/rustfix/src/diagnostics.rs b/vendor/rustfix/src/diagnostics.rs
new file mode 100644
index 000000000..f745b0b2a
--- /dev/null
+++ b/vendor/rustfix/src/diagnostics.rs
@@ -0,0 +1,89 @@
+//! Rustc Diagnostic JSON Output
+//!
+//! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/de78655bca47cac8e783dbb563e7e5c25c1fae40/src/libsyntax/json.rs)
+
+use serde::Deserialize;
+
+#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)]
+pub struct Diagnostic {
+ /// The primary error message.
+ pub message: String,
+ pub code: Option<DiagnosticCode>,
+ /// "error: internal compiler error", "error", "warning", "note", "help".
+ level: String,
+ pub spans: Vec<DiagnosticSpan>,
+ /// Associated diagnostic messages.
+ pub children: Vec<Diagnostic>,
+ /// The message as rustc would render it. Currently this is only
+ /// `Some` for "suggestions", but eventually it will include all
+ /// snippets.
+ pub rendered: Option<String>,
+}
+
+#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)]
+pub struct DiagnosticSpan {
+ pub file_name: String,
+ pub byte_start: u32,
+ pub byte_end: u32,
+ /// 1-based.
+ pub line_start: usize,
+ pub line_end: usize,
+ /// 1-based, character offset.
+ pub column_start: usize,
+ pub column_end: usize,
+ /// Is this a "primary" span -- meaning the point, or one of the points,
+ /// where the error occurred?
+ pub is_primary: bool,
+ /// Source text from the start of line_start to the end of line_end.
+ pub text: Vec<DiagnosticSpanLine>,
+ /// Label that should be placed at this location (if any)
+ label: Option<String>,
+ /// If we are suggesting a replacement, this will contain text
+ /// that should be sliced in atop this span. You may prefer to
+ /// load the fully rendered version from the parent `Diagnostic`,
+ /// however.
+ pub suggested_replacement: Option<String>,
+ pub suggestion_applicability: Option<Applicability>,
+ /// Macro invocations that created the code at this span, if any.
+ expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
+pub enum Applicability {
+ MachineApplicable,
+ HasPlaceholders,
+ MaybeIncorrect,
+ Unspecified,
+}
+
+#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
+pub struct DiagnosticSpanLine {
+ pub text: String,
+
+ /// 1-based, character offset in self.text.
+ pub highlight_start: usize,
+
+ pub highlight_end: usize,
+}
+
+#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
+struct DiagnosticSpanMacroExpansion {
+ /// span where macro was applied to generate this code; note that
+ /// this may itself derive from a macro (if
+ /// `span.expansion.is_some()`)
+ span: DiagnosticSpan,
+
+ /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
+ macro_decl_name: String,
+
+ /// span where macro was defined (if known)
+ def_site_span: Option<DiagnosticSpan>,
+}
+
+#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)]
+pub struct DiagnosticCode {
+ /// The code itself.
+ pub code: String,
+ /// An explanation for the code.
+ explanation: Option<String>,
+}
diff --git a/vendor/rustfix/src/lib.rs b/vendor/rustfix/src/lib.rs
new file mode 100644
index 000000000..f83223306
--- /dev/null
+++ b/vendor/rustfix/src/lib.rs
@@ -0,0 +1,263 @@
+#![warn(rust_2018_idioms)]
+
+#[macro_use]
+extern crate log;
+#[cfg(test)]
+#[macro_use]
+extern crate proptest;
+
+use std::collections::HashSet;
+use std::ops::Range;
+
+use anyhow::Error;
+
+pub mod diagnostics;
+use crate::diagnostics::{Diagnostic, DiagnosticSpan};
+mod replace;
+
+#[derive(Debug, Clone, Copy)]
+pub enum Filter {
+ MachineApplicableOnly,
+ Everything,
+}
+
+pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>(
+ input: &str,
+ only: &HashSet<String, S>,
+ filter: Filter,
+) -> serde_json::error::Result<Vec<Suggestion>> {
+ let mut result = Vec::new();
+ for cargo_msg in serde_json::Deserializer::from_str(input).into_iter::<Diagnostic>() {
+ // One diagnostic line might have multiple suggestions
+ result.extend(collect_suggestions(&cargo_msg?, only, filter));
+ }
+ Ok(result)
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct LinePosition {
+ pub line: usize,
+ pub column: usize,
+}
+
+impl std::fmt::Display for LinePosition {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}:{}", self.line, self.column)
+ }
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct LineRange {
+ pub start: LinePosition,
+ pub end: LinePosition,
+}
+
+impl std::fmt::Display for LineRange {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}-{}", self.start, self.end)
+ }
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+/// An error/warning and possible solutions for fixing it
+pub struct Suggestion {
+ pub message: String,
+ pub snippets: Vec<Snippet>,
+ pub solutions: Vec<Solution>,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Solution {
+ pub message: String,
+ pub replacements: Vec<Replacement>,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Snippet {
+ pub file_name: String,
+ pub line_range: LineRange,
+ pub range: Range<usize>,
+ /// leading surrounding text, text to replace, trailing surrounding text
+ ///
+ /// This split is useful for higlighting the part that gets replaced
+ pub text: (String, String, String),
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Replacement {
+ pub snippet: Snippet,
+ pub replacement: String,
+}
+
+fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
+ // unindent the snippet
+ let indent = span
+ .text
+ .iter()
+ .map(|line| {
+ let indent = line
+ .text
+ .chars()
+ .take_while(|&c| char::is_whitespace(c))
+ .count();
+ std::cmp::min(indent, line.highlight_start - 1)
+ })
+ .min()?;
+
+ let text_slice = span.text[0].text.chars().collect::<Vec<char>>();
+
+ // We subtract `1` because these highlights are 1-based
+ // Check the `min` so that it doesn't attempt to index out-of-bounds when
+ // the span points to the "end" of the line. For example, a line of
+ // "foo\n" with a highlight_start of 5 is intended to highlight *after*
+ // the line. This needs to compensate since the newline has been removed
+ // from the text slice.
+ let start = (span.text[0].highlight_start - 1).min(text_slice.len());
+ let end = (span.text[0].highlight_end - 1).min(text_slice.len());
+ let lead = text_slice[indent..start].iter().collect();
+ let mut body: String = text_slice[start..end].iter().collect();
+
+ for line in span.text.iter().take(span.text.len() - 1).skip(1) {
+ body.push('\n');
+ body.push_str(&line.text[indent..]);
+ }
+ let mut tail = String::new();
+ let last = &span.text[span.text.len() - 1];
+
+ // If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of
+ // bounds' access by making sure the index is within the array bounds.
+ // `saturating_sub` is used in case of an empty file
+ let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1);
+ let last_slice = last.text.chars().collect::<Vec<char>>();
+
+ if span.text.len() > 1 {
+ body.push('\n');
+ body.push_str(
+ &last_slice[indent..last_tail_index]
+ .iter()
+ .collect::<String>(),
+ );
+ }
+ tail.push_str(&last_slice[last_tail_index..].iter().collect::<String>());
+ Some(Snippet {
+ file_name: span.file_name.clone(),
+ line_range: LineRange {
+ start: LinePosition {
+ line: span.line_start,
+ column: span.column_start,
+ },
+ end: LinePosition {
+ line: span.line_end,
+ column: span.column_end,
+ },
+ },
+ range: (span.byte_start as usize)..(span.byte_end as usize),
+ text: (lead, body, tail),
+ })
+}
+
+fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> {
+ let snippet = parse_snippet(span)?;
+ let replacement = span.suggested_replacement.clone()?;
+ Some(Replacement {
+ snippet,
+ replacement,
+ })
+}
+
+pub fn collect_suggestions<S: ::std::hash::BuildHasher>(
+ diagnostic: &Diagnostic,
+ only: &HashSet<String, S>,
+ filter: Filter,
+) -> Option<Suggestion> {
+ if !only.is_empty() {
+ if let Some(ref code) = diagnostic.code {
+ if !only.contains(&code.code) {
+ // This is not the code we are looking for
+ return None;
+ }
+ } else {
+ // No code, probably a weird builtin warning/error
+ return None;
+ }
+ }
+
+ let snippets = diagnostic.spans.iter().filter_map(parse_snippet).collect();
+
+ let solutions: Vec<_> = diagnostic
+ .children
+ .iter()
+ .filter_map(|child| {
+ let replacements: Vec<_> = child
+ .spans
+ .iter()
+ .filter(|span| {
+ use crate::diagnostics::Applicability::*;
+ use crate::Filter::*;
+
+ match (filter, &span.suggestion_applicability) {
+ (MachineApplicableOnly, Some(MachineApplicable)) => true,
+ (MachineApplicableOnly, _) => false,
+ (Everything, _) => true,
+ }
+ })
+ .filter_map(collect_span)
+ .collect();
+ if !replacements.is_empty() {
+ Some(Solution {
+ message: child.message.clone(),
+ replacements,
+ })
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ if solutions.is_empty() {
+ None
+ } else {
+ Some(Suggestion {
+ message: diagnostic.message.clone(),
+ snippets,
+ solutions,
+ })
+ }
+}
+
+pub struct CodeFix {
+ data: replace::Data,
+}
+
+impl CodeFix {
+ pub fn new(s: &str) -> CodeFix {
+ CodeFix {
+ data: replace::Data::new(s.as_bytes()),
+ }
+ }
+
+ pub fn apply(&mut self, suggestion: &Suggestion) -> Result<(), Error> {
+ for sol in &suggestion.solutions {
+ for r in &sol.replacements {
+ self.data.replace_range(
+ r.snippet.range.start,
+ r.snippet.range.end.saturating_sub(1),
+ r.replacement.as_bytes(),
+ )?;
+ }
+ }
+ Ok(())
+ }
+
+ pub fn finish(&self) -> Result<String, Error> {
+ Ok(String::from_utf8(self.data.to_vec())?)
+ }
+}
+
+pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result<String, Error> {
+ let mut fix = CodeFix::new(code);
+ for suggestion in suggestions.iter().rev() {
+ fix.apply(suggestion)?;
+ }
+ fix.finish()
+}
diff --git a/vendor/rustfix/src/replace.rs b/vendor/rustfix/src/replace.rs
new file mode 100644
index 000000000..8deba5e9f
--- /dev/null
+++ b/vendor/rustfix/src/replace.rs
@@ -0,0 +1,323 @@
+//! A small module giving you a simple container that allows easy and cheap
+//! replacement of parts of its content, with the ability to prevent changing
+//! the same parts multiple times.
+
+use anyhow::{anyhow, ensure, Error};
+use std::rc::Rc;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum State {
+ Initial,
+ Replaced(Rc<[u8]>),
+ Inserted(Rc<[u8]>),
+}
+
+impl State {
+ fn is_inserted(&self) -> bool {
+ matches!(*self, State::Inserted(..))
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct Span {
+ /// Start of this span in parent data
+ start: usize,
+ /// up to end including
+ end: usize,
+ data: State,
+}
+
+/// A container that allows easily replacing chunks of its data
+#[derive(Debug, Clone, Default)]
+pub struct Data {
+ original: Vec<u8>,
+ parts: Vec<Span>,
+}
+
+impl Data {
+ /// Create a new data container from a slice of bytes
+ pub fn new(data: &[u8]) -> Self {
+ Data {
+ original: data.into(),
+ parts: vec![Span {
+ data: State::Initial,
+ start: 0,
+ end: data.len().saturating_sub(1),
+ }],
+ }
+ }
+
+ /// Render this data as a vector of bytes
+ pub fn to_vec(&self) -> Vec<u8> {
+ if self.original.is_empty() {
+ return Vec::new();
+ }
+
+ self.parts.iter().fold(Vec::new(), |mut acc, d| {
+ match d.data {
+ State::Initial => acc.extend_from_slice(&self.original[d.start..=d.end]),
+ State::Replaced(ref d) | State::Inserted(ref d) => acc.extend_from_slice(d),
+ };
+ acc
+ })
+ }
+
+ /// Replace a chunk of data with the given slice, erroring when this part
+ /// was already changed previously.
+ pub fn replace_range(
+ &mut self,
+ from: usize,
+ up_to_and_including: usize,
+ data: &[u8],
+ ) -> Result<(), Error> {
+ let exclusive_end = up_to_and_including + 1;
+
+ ensure!(
+ from <= exclusive_end,
+ "Invalid range {}...{}, start is larger than end",
+ from,
+ up_to_and_including
+ );
+
+ ensure!(
+ up_to_and_including <= self.original.len(),
+ "Invalid range {}...{} given, original data is only {} byte long",
+ from,
+ up_to_and_including,
+ self.original.len()
+ );
+
+ let insert_only = from == exclusive_end;
+
+ // Since we error out when replacing an already replaced chunk of data,
+ // we can take some shortcuts here. For example, there can be no
+ // overlapping replacements -- we _always_ split a chunk of 'initial'
+ // data into three[^empty] parts, and there can't ever be two 'initial'
+ // parts touching.
+ //
+ // [^empty]: Leading and trailing ones might be empty if we replace
+ // the whole chunk. As an optimization and without loss of generality we
+ // don't add empty parts.
+ let new_parts = {
+ let index_of_part_to_split = self
+ .parts
+ .iter()
+ .position(|p| {
+ !p.data.is_inserted() && p.start <= from && p.end >= up_to_and_including
+ })
+ .ok_or_else(|| {
+ use log::Level::Debug;
+ if log_enabled!(Debug) {
+ let slices = self
+ .parts
+ .iter()
+ .map(|p| {
+ (
+ p.start,
+ p.end,
+ match p.data {
+ State::Initial => "initial",
+ State::Replaced(..) => "replaced",
+ State::Inserted(..) => "inserted",
+ },
+ )
+ })
+ .collect::<Vec<_>>();
+ debug!(
+ "no single slice covering {}...{}, current slices: {:?}",
+ from, up_to_and_including, slices,
+ );
+ }
+
+ anyhow!(
+ "Could not replace range {}...{} in file \
+ -- maybe parts of it were already replaced?",
+ from,
+ up_to_and_including
+ )
+ })?;
+
+ let part_to_split = &self.parts[index_of_part_to_split];
+
+ // If this replacement matches exactly the part that we would
+ // otherwise split then we ignore this for now. This means that you
+ // can replace the exact same range with the exact same content
+ // multiple times and we'll process and allow it.
+ //
+ // This is currently done to alleviate issues like
+ // rust-lang/rust#51211 although this clause likely wants to be
+ // removed if that's fixed deeper in the compiler.
+ if part_to_split.start == from && part_to_split.end == up_to_and_including {
+ if let State::Replaced(ref replacement) = part_to_split.data {
+ if &**replacement == data {
+ return Ok(());
+ }
+ }
+ }
+
+ ensure!(
+ part_to_split.data == State::Initial,
+ "Cannot replace slice of data that was already replaced"
+ );
+
+ let mut new_parts = Vec::with_capacity(self.parts.len() + 2);
+
+ // Previous parts
+ if let Some(ps) = self.parts.get(..index_of_part_to_split) {
+ new_parts.extend_from_slice(ps);
+ }
+
+ // Keep initial data on left side of part
+ if from > part_to_split.start {
+ new_parts.push(Span {
+ start: part_to_split.start,
+ end: from.saturating_sub(1),
+ data: State::Initial,
+ });
+ }
+
+ // New part
+ new_parts.push(Span {
+ start: from,
+ end: up_to_and_including,
+ data: if insert_only {
+ State::Inserted(data.into())
+ } else {
+ State::Replaced(data.into())
+ },
+ });
+
+ // Keep initial data on right side of part
+ if up_to_and_including < part_to_split.end {
+ new_parts.push(Span {
+ start: up_to_and_including + 1,
+ end: part_to_split.end,
+ data: State::Initial,
+ });
+ }
+
+ // Following parts
+ if let Some(ps) = self.parts.get(index_of_part_to_split + 1..) {
+ new_parts.extend_from_slice(ps);
+ }
+
+ new_parts
+ };
+
+ self.parts = new_parts;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use proptest::prelude::*;
+
+ fn str(i: &[u8]) -> &str {
+ ::std::str::from_utf8(i).unwrap()
+ }
+
+ #[test]
+ fn replace_some_stuff() {
+ let mut d = Data::new(b"foo bar baz");
+ d.replace_range(4, 6, b"lol").unwrap();
+ assert_eq!("foo lol baz", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_a_single_char() {
+ let mut d = Data::new(b"let y = true;");
+ d.replace_range(4, 4, b"mut y").unwrap();
+ assert_eq!("let mut y = true;", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_multiple_lines() {
+ let mut d = Data::new(b"lorem\nipsum\ndolor");
+
+ d.replace_range(6, 10, b"lol").unwrap();
+ assert_eq!("lorem\nlol\ndolor", str(&d.to_vec()));
+
+ d.replace_range(12, 16, b"lol").unwrap();
+ assert_eq!("lorem\nlol\nlol", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_multiple_lines_with_insert_only() {
+ let mut d = Data::new(b"foo!");
+
+ d.replace_range(3, 2, b"bar").unwrap();
+ assert_eq!("foobar!", str(&d.to_vec()));
+
+ d.replace_range(0, 2, b"baz").unwrap();
+ assert_eq!("bazbar!", str(&d.to_vec()));
+
+ d.replace_range(3, 3, b"?").unwrap();
+ assert_eq!("bazbar?", str(&d.to_vec()));
+ }
+
+ #[test]
+ fn replace_invalid_range() {
+ let mut d = Data::new(b"foo!");
+
+ assert!(d.replace_range(2, 0, b"bar").is_err());
+ assert!(d.replace_range(0, 2, b"bar").is_ok());
+ }
+
+ #[test]
+ fn empty_to_vec_roundtrip() {
+ let s = "";
+ assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
+ }
+
+ #[test]
+ #[should_panic(expected = "Cannot replace slice of data that was already replaced")]
+ fn replace_overlapping_stuff_errs() {
+ let mut d = Data::new(b"foo bar baz");
+
+ d.replace_range(4, 6, b"lol").unwrap();
+ assert_eq!("foo lol baz", str(&d.to_vec()));
+
+ d.replace_range(4, 6, b"lol2").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "original data is only 3 byte long")]
+ fn broken_replacements() {
+ let mut d = Data::new(b"foo");
+ d.replace_range(4, 7, b"lol").unwrap();
+ }
+
+ #[test]
+ fn replace_same_twice() {
+ let mut d = Data::new(b"foo");
+ d.replace_range(0, 0, b"b").unwrap();
+ d.replace_range(0, 0, b"b").unwrap();
+ assert_eq!("boo", str(&d.to_vec()));
+ }
+
+ proptest! {
+ #[test]
+ #[ignore]
+ fn new_to_vec_roundtrip(ref s in "\\PC*") {
+ assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
+ }
+
+ #[test]
+ #[ignore]
+ fn replace_random_chunks(
+ ref data in "\\PC*",
+ ref replacements in prop::collection::vec(
+ (any::<::std::ops::Range<usize>>(), any::<Vec<u8>>()),
+ 1..1337,
+ )
+ ) {
+ let mut d = Data::new(data.as_bytes());
+ for &(ref range, ref bytes) in replacements {
+ let _ = d.replace_range(range.start, range.end, bytes);
+ }
+ }
+ }
+}