]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/config/file_lines.rs
Auto merge of #85290 - Amanieu:asm_const_int, r=nagisa
[rust.git] / src / tools / rustfmt / src / config / file_lines.rs
1 //! This module contains types and functions to support formatting specific line ranges.
2
3 use itertools::Itertools;
4 use std::collections::HashMap;
5 use std::path::PathBuf;
6 use std::{cmp, fmt, iter, str};
7
8 use rustc_data_structures::sync::Lrc;
9 use rustc_span::{self, SourceFile};
10 use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
11 use serde_json as json;
12 use thiserror::Error;
13
14 /// A range of lines in a file, inclusive of both ends.
15 pub struct LineRange {
16     pub file: Lrc<SourceFile>,
17     pub lo: usize,
18     pub hi: usize,
19 }
20
21 /// Defines the name of an input - either a file or stdin.
22 #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
23 pub enum FileName {
24     Real(PathBuf),
25     Stdin,
26 }
27
28 impl From<rustc_span::FileName> for FileName {
29     fn from(name: rustc_span::FileName) -> FileName {
30         match name {
31             rustc_span::FileName::Real(rustc_span::RealFileName::LocalPath(p)) => FileName::Real(p),
32             rustc_span::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
33             _ => unreachable!(),
34         }
35     }
36 }
37
38 impl fmt::Display for FileName {
39     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40         match self {
41             FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
42             FileName::Stdin => write!(f, "stdin"),
43         }
44     }
45 }
46
47 impl<'de> Deserialize<'de> for FileName {
48     fn deserialize<D>(deserializer: D) -> Result<FileName, D::Error>
49     where
50         D: Deserializer<'de>,
51     {
52         let s = String::deserialize(deserializer)?;
53         if s == "stdin" {
54             Ok(FileName::Stdin)
55         } else {
56             Ok(FileName::Real(s.into()))
57         }
58     }
59 }
60
61 impl Serialize for FileName {
62     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63     where
64         S: Serializer,
65     {
66         let s = match self {
67             FileName::Stdin => Ok("stdin"),
68             FileName::Real(path) => path
69                 .to_str()
70                 .ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")),
71         };
72
73         s.and_then(|s| serializer.serialize_str(s))
74     }
75 }
76
77 impl LineRange {
78     pub fn file_name(&self) -> FileName {
79         self.file.name.clone().into()
80     }
81 }
82
83 /// A range that is inclusive of both ends.
84 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)]
85 pub struct Range {
86     lo: usize,
87     hi: usize,
88 }
89
90 impl<'a> From<&'a LineRange> for Range {
91     fn from(range: &'a LineRange) -> Range {
92         Range::new(range.lo, range.hi)
93     }
94 }
95
96 impl fmt::Display for Range {
97     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98         write!(f, "{}..{}", self.lo, self.hi)
99     }
100 }
101
102 impl Range {
103     pub fn new(lo: usize, hi: usize) -> Range {
104         Range { lo, hi }
105     }
106
107     fn is_empty(self) -> bool {
108         self.lo > self.hi
109     }
110
111     #[allow(dead_code)]
112     fn contains(self, other: Range) -> bool {
113         if other.is_empty() {
114             true
115         } else {
116             !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
117         }
118     }
119
120     fn intersects(self, other: Range) -> bool {
121         if self.is_empty() || other.is_empty() {
122             false
123         } else {
124             (self.lo <= other.hi && other.hi <= self.hi)
125                 || (other.lo <= self.hi && self.hi <= other.hi)
126         }
127     }
128
129     fn adjacent_to(self, other: Range) -> bool {
130         if self.is_empty() || other.is_empty() {
131             false
132         } else {
133             self.hi + 1 == other.lo || other.hi + 1 == self.lo
134         }
135     }
136
137     /// Returns a new `Range` with lines from `self` and `other` if they were adjacent or
138     /// intersect; returns `None` otherwise.
139     fn merge(self, other: Range) -> Option<Range> {
140         if self.adjacent_to(other) || self.intersects(other) {
141             Some(Range::new(
142                 cmp::min(self.lo, other.lo),
143                 cmp::max(self.hi, other.hi),
144             ))
145         } else {
146             None
147         }
148     }
149 }
150
151 /// A set of lines in files.
152 ///
153 /// It is represented as a multimap keyed on file names, with values a collection of
154 /// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all
155 /// lines in all files.
156 #[derive(Clone, Debug, Default, PartialEq)]
157 pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
158
159 impl fmt::Display for FileLines {
160     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161         match &self.0 {
162             None => write!(f, "None")?,
163             Some(map) => {
164                 for (file_name, ranges) in map.iter() {
165                     write!(f, "{}: ", file_name)?;
166                     write!(f, "{}\n", ranges.iter().format(", "))?;
167                 }
168             }
169         };
170         Ok(())
171     }
172 }
173
174 /// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
175 /// and ordered by their start point.
176 fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
177     for ranges in ranges.values_mut() {
178         ranges.sort();
179         let mut result = vec![];
180         let mut iter = ranges.iter_mut().peekable();
181         while let Some(next) = iter.next() {
182             let mut next = *next;
183             while let Some(&&mut peek) = iter.peek() {
184                 if let Some(merged) = next.merge(peek) {
185                     iter.next().unwrap();
186                     next = merged;
187                 } else {
188                     break;
189                 }
190             }
191             result.push(next)
192         }
193         *ranges = result;
194     }
195 }
196
197 impl FileLines {
198     /// Creates a `FileLines` that contains all lines in all files.
199     pub(crate) fn all() -> FileLines {
200         FileLines(None)
201     }
202
203     /// Returns `true` if this `FileLines` contains all lines in all files.
204     pub(crate) fn is_all(&self) -> bool {
205         self.0.is_none()
206     }
207
208     pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
209         normalize_ranges(&mut ranges);
210         FileLines(Some(ranges))
211     }
212
213     /// Returns an iterator over the files contained in `self`.
214     pub fn files(&self) -> Files<'_> {
215         Files(self.0.as_ref().map(HashMap::keys))
216     }
217
218     /// Returns JSON representation as accepted by the `--file-lines JSON` arg.
219     pub fn to_json_spans(&self) -> Vec<JsonSpan> {
220         match &self.0 {
221             None => vec![],
222             Some(file_ranges) => file_ranges
223                 .iter()
224                 .flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r)))
225                 .map(|(file, range)| JsonSpan {
226                     file: file.to_owned(),
227                     range: (range.lo, range.hi),
228                 })
229                 .collect(),
230         }
231     }
232
233     /// Returns `true` if `self` includes all lines in all files. Otherwise runs `f` on all ranges
234     /// in the designated file (if any) and returns true if `f` ever does.
235     fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
236     where
237         F: FnMut(&Range) -> bool,
238     {
239         let map = match self.0 {
240             // `None` means "all lines in all files".
241             None => return true,
242             Some(ref map) => map,
243         };
244
245         match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) {
246             Some(ranges) => ranges.iter().any(f),
247             None => false,
248         }
249     }
250
251     /// Returns `true` if `range` is fully contained in `self`.
252     #[allow(dead_code)]
253     pub(crate) fn contains(&self, range: &LineRange) -> bool {
254         self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
255     }
256
257     /// Returns `true` if any lines in `range` are in `self`.
258     pub(crate) fn intersects(&self, range: &LineRange) -> bool {
259         self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
260     }
261
262     /// Returns `true` if `line` from `file_name` is in `self`.
263     pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
264         self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
265     }
266
267     /// Returns `true` if all the lines between `lo` and `hi` from `file_name` are in `self`.
268     pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
269         self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi)))
270     }
271 }
272
273 /// `FileLines` files iterator.
274 pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec<Range>>>);
275
276 impl<'a> iter::Iterator for Files<'a> {
277     type Item = &'a FileName;
278
279     fn next(&mut self) -> Option<&'a FileName> {
280         self.0.as_mut().and_then(Iterator::next)
281     }
282 }
283
284 fn canonicalize_path_string(file: &FileName) -> Option<FileName> {
285     match *file {
286         FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real),
287         _ => Some(file.clone()),
288     }
289 }
290
291 #[derive(Error, Debug)]
292 pub enum FileLinesError {
293     #[error("{0}")]
294     Json(json::Error),
295     #[error("Can't canonicalize {0}")]
296     CannotCanonicalize(FileName),
297 }
298
299 // This impl is needed for `Config::override_value` to work for use in tests.
300 impl str::FromStr for FileLines {
301     type Err = FileLinesError;
302
303     fn from_str(s: &str) -> Result<FileLines, Self::Err> {
304         let v: Vec<JsonSpan> = json::from_str(s).map_err(FileLinesError::Json)?;
305         let mut m = HashMap::new();
306         for js in v {
307             let (s, r) = JsonSpan::into_tuple(js)?;
308             m.entry(s).or_insert_with(|| vec![]).push(r);
309         }
310         Ok(FileLines::from_ranges(m))
311     }
312 }
313
314 // For JSON decoding.
315 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
316 pub struct JsonSpan {
317     file: FileName,
318     range: (usize, usize),
319 }
320
321 impl JsonSpan {
322     fn into_tuple(self) -> Result<(FileName, Range), FileLinesError> {
323         let (lo, hi) = self.range;
324         let canonical = canonicalize_path_string(&self.file)
325             .ok_or_else(|| FileLinesError::CannotCanonicalize(self.file))?;
326         Ok((canonical, Range::new(lo, hi)))
327     }
328 }
329
330 // This impl is needed for inclusion in the `Config` struct. We don't have a toml representation
331 // for `FileLines`, so it will just panic instead.
332 impl<'de> ::serde::de::Deserialize<'de> for FileLines {
333     fn deserialize<D>(_: D) -> Result<Self, D::Error>
334     where
335         D: ::serde::de::Deserializer<'de>,
336     {
337         panic!(
338             "FileLines cannot be deserialized from a project rustfmt.toml file: please \
339              specify it via the `--file-lines` option instead"
340         );
341     }
342 }
343
344 // We also want to avoid attempting to serialize a FileLines to toml. The
345 // `Config` struct should ensure this impl is never reached.
346 impl ::serde::ser::Serialize for FileLines {
347     fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
348     where
349         S: ::serde::ser::Serializer,
350     {
351         unreachable!("FileLines cannot be serialized. This is a rustfmt bug.");
352     }
353 }
354
355 #[cfg(test)]
356 mod test {
357     use super::Range;
358
359     #[test]
360     fn test_range_intersects() {
361         assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
362         assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
363         assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
364         assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
365         assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
366     }
367
368     #[test]
369     fn test_range_adjacent_to() {
370         assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
371         assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
372         assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
373         assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
374         assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
375     }
376
377     #[test]
378     fn test_range_contains() {
379         assert!(Range::new(1, 2).contains(Range::new(1, 1)));
380         assert!(Range::new(1, 2).contains(Range::new(2, 2)));
381         assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
382         assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
383     }
384
385     #[test]
386     fn test_range_merge() {
387         assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
388         assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
389         assert_eq!(
390             Some(Range::new(3, 7)),
391             Range::new(3, 5).merge(Range::new(4, 7))
392         );
393         assert_eq!(
394             Some(Range::new(3, 7)),
395             Range::new(3, 5).merge(Range::new(5, 7))
396         );
397         assert_eq!(
398             Some(Range::new(3, 7)),
399             Range::new(3, 5).merge(Range::new(6, 7))
400         );
401         assert_eq!(
402             Some(Range::new(3, 7)),
403             Range::new(3, 7).merge(Range::new(4, 5))
404         );
405     }
406
407     use super::json::{self, json};
408     use super::{FileLines, FileName};
409     use std::{collections::HashMap, path::PathBuf};
410
411     #[test]
412     fn file_lines_to_json() {
413         let ranges: HashMap<FileName, Vec<Range>> = [
414             (
415                 FileName::Real(PathBuf::from("src/main.rs")),
416                 vec![Range::new(1, 3), Range::new(5, 7)],
417             ),
418             (
419                 FileName::Real(PathBuf::from("src/lib.rs")),
420                 vec![Range::new(1, 7)],
421             ),
422         ]
423         .iter()
424         .cloned()
425         .collect();
426
427         let file_lines = FileLines::from_ranges(ranges);
428         let mut spans = file_lines.to_json_spans();
429         spans.sort();
430         let json = json::to_value(&spans).unwrap();
431         assert_eq!(
432             json,
433             json! {[
434                 {"file": "src/lib.rs",  "range": [1, 7]},
435                 {"file": "src/main.rs", "range": [1, 3]},
436                 {"file": "src/main.rs", "range": [5, 7]},
437             ]}
438         );
439     }
440 }