| #![allow(rustc::symbol_intern_string_literal)] | |
| use std::io::prelude::*; | |
| use std::iter::Peekable; | |
| use std::path::PathBuf; | |
| use std::sync::{Arc, Mutex}; | |
| use std::{assert_matches, io, str}; | |
| use ast::token::IdentKind; | |
| use rustc_ast::token::{self, Delimiter, Token}; | |
| use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; | |
| use rustc_ast::{self as ast, PatKind, visit}; | |
| use rustc_ast_pretty::pprust::item_to_string; | |
| use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; | |
| use rustc_errors::emitter::OutputTheme; | |
| use rustc_errors::{AutoStream, DiagCtxt, MultiSpan, PResult}; | |
| use rustc_session::parse::ParseSess; | |
| use rustc_span::source_map::{FilePathMapping, SourceMap}; | |
| use rustc_span::{ | |
| BytePos, FileName, Pos, Span, Symbol, create_default_session_globals_then, kw, sym, | |
| }; | |
| use crate::lexer::StripTokens; | |
| use crate::parser::{AllowConstBlockItems, ForceCollect, Parser}; | |
| use crate::{new_parser_from_source_str, source_str_to_stream, unwrap_or_emit_fatal}; | |
| fn filename(sm: &SourceMap, path: &str) -> FileName { | |
| FileName::Real(sm.path_mapping().to_real_filename(sm.working_dir(), PathBuf::from(path))) | |
| } | |
| /// Map string to parser (via tts). | |
| fn string_to_parser(psess: &ParseSess, source_str: String) -> Parser<'_> { | |
| unwrap_or_emit_fatal(new_parser_from_source_str( | |
| psess, | |
| filename(psess.source_map(), "bogofile"), | |
| source_str, | |
| StripTokens::Nothing, | |
| )) | |
| } | |
| fn create_test_handler(theme: OutputTheme) -> (DiagCtxt, Arc<SourceMap>, Arc<Mutex<Vec<u8>>>) { | |
| let output = Arc::new(Mutex::new(Vec::new())); | |
| let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); | |
| let shared: Box<dyn Write + Send> = Box::new(Shared { data: output.clone() }); | |
| let auto_stream = AutoStream::never(shared); | |
| let dcx = DiagCtxt::new(Box::new( | |
| AnnotateSnippetEmitter::new(auto_stream) | |
| .sm(Some(source_map.clone())) | |
| .diagnostic_width(Some(140)) | |
| .theme(theme), | |
| )); | |
| (dcx, source_map, output) | |
| } | |
| /// Returns the result of parsing the given string via the given callback. | |
| /// | |
| /// If there are any errors, this will panic. | |
| fn with_error_checking_parse<'a, T, F>(s: String, psess: &'a ParseSess, f: F) -> T | |
| where | |
| F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>, | |
| { | |
| let mut p = string_to_parser(&psess, s); | |
| let x = f(&mut p).unwrap(); | |
| p.dcx().abort_if_errors(); | |
| x | |
| } | |
| /// Verifies that parsing the given string using the given callback will | |
| /// generate an error that contains the given text. | |
| fn with_expected_parse_error<T, F>(source_str: &str, expected_output: &str, f: F) | |
| where | |
| F: for<'a> FnOnce(&mut Parser<'a>) -> PResult<'a, T>, | |
| { | |
| let (handler, source_map, output) = create_test_handler(OutputTheme::Ascii); | |
| let psess = ParseSess::with_dcx(handler, source_map); | |
| let mut p = string_to_parser(&psess, source_str.to_string()); | |
| let result = f(&mut p); | |
| assert!(result.is_ok()); | |
| let bytes = output.lock().unwrap(); | |
| let actual_output = str::from_utf8(&bytes).unwrap(); | |
| println!("expected output:\n------\n{}------", expected_output); | |
| println!("actual output:\n------\n{}------", actual_output); | |
| assert!(actual_output.contains(expected_output)) | |
| } | |
| /// Maps a string to tts, using a made-up filename. | |
| pub(crate) fn string_to_stream(source_str: String) -> TokenStream { | |
| let psess = ParseSess::new(); | |
| unwrap_or_emit_fatal(source_str_to_stream( | |
| &psess, | |
| filename(psess.source_map(), "bogofile"), | |
| source_str, | |
| None, | |
| )) | |
| } | |
| /// Does the given string match the pattern? whitespace in the first string | |
| /// may be deleted or replaced with other whitespace to match the pattern. | |
| /// This function is relatively Unicode-ignorant; fortunately, the careful design | |
| /// of UTF-8 mitigates this ignorance. It doesn't do NKF-normalization(?). | |
| pub(crate) fn matches_codepattern(a: &str, b: &str) -> bool { | |
| let mut a_iter = a.chars().peekable(); | |
| let mut b_iter = b.chars().peekable(); | |
| loop { | |
| let (a, b) = match (a_iter.peek(), b_iter.peek()) { | |
| (None, None) => return true, | |
| (None, _) => return false, | |
| (Some(&a), None) => { | |
| if rustc_lexer::is_whitespace(a) { | |
| break; // Trailing whitespace check is out of loop for borrowck. | |
| } else { | |
| return false; | |
| } | |
| } | |
| (Some(&a), Some(&b)) => (a, b), | |
| }; | |
| if rustc_lexer::is_whitespace(a) && rustc_lexer::is_whitespace(b) { | |
| // Skip whitespace for `a` and `b`. | |
| scan_for_non_ws_or_end(&mut a_iter); | |
| scan_for_non_ws_or_end(&mut b_iter); | |
| } else if rustc_lexer::is_whitespace(a) { | |
| // Skip whitespace for `a`. | |
| scan_for_non_ws_or_end(&mut a_iter); | |
| } else if a == b { | |
| a_iter.next(); | |
| b_iter.next(); | |
| } else { | |
| return false; | |
| } | |
| } | |
| // Check if a has *only* trailing whitespace. | |
| a_iter.all(rustc_lexer::is_whitespace) | |
| } | |
| /// Advances the given peekable `Iterator` until it reaches a non-whitespace character. | |
| fn scan_for_non_ws_or_end<I: Iterator<Item = char>>(iter: &mut Peekable<I>) { | |
| while iter.peek().copied().is_some_and(rustc_lexer::is_whitespace) { | |
| iter.next(); | |
| } | |
| } | |
| /// Identifies a position in the text by the n'th occurrence of a string. | |
| struct Position { | |
| string: &'static str, | |
| count: usize, | |
| } | |
| struct SpanLabel { | |
| start: Position, | |
| end: Position, | |
| label: &'static str, | |
| } | |
| struct Shared<T: Write> { | |
| data: Arc<Mutex<T>>, | |
| } | |
| impl<T: Write> Write for Shared<T> { | |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
| self.data.lock().unwrap().write(buf) | |
| } | |
| fn flush(&mut self) -> io::Result<()> { | |
| self.data.lock().unwrap().flush() | |
| } | |
| } | |
| fn test_harness( | |
| file_text: &str, | |
| span_labels: Vec<SpanLabel>, | |
| notes: Vec<(Option<(Position, Position)>, &'static str)>, | |
| expected_output_ascii: &str, | |
| expected_output_unicode: &str, | |
| ) { | |
| create_default_session_globals_then(|| { | |
| for (theme, expected_output) in [ | |
| (OutputTheme::Ascii, expected_output_ascii), | |
| (OutputTheme::Unicode, expected_output_unicode), | |
| ] { | |
| let (dcx, source_map, output) = create_test_handler(theme); | |
| source_map.new_source_file(filename(&source_map, "test.rs"), file_text.to_owned()); | |
| let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); | |
| let mut msp = MultiSpan::from_span(primary_span); | |
| for span_label in &span_labels { | |
| let span = make_span(&file_text, &span_label.start, &span_label.end); | |
| msp.push_span_label(span, span_label.label); | |
| println!("span: {:?} label: {:?}", span, span_label.label); | |
| println!("text: {:?}", source_map.span_to_snippet(span)); | |
| } | |
| let mut err = dcx.handle().struct_span_err(msp, "foo"); | |
| for (position, note) in ¬es { | |
| if let Some((start, end)) = position { | |
| let span = make_span(&file_text, &start, &end); | |
| err.span_note(span, *note); | |
| } else { | |
| err.note(*note); | |
| } | |
| } | |
| err.emit(); | |
| assert!( | |
| expected_output.chars().next() == Some('\n'), | |
| "expected output should begin with newline" | |
| ); | |
| let expected_output = &expected_output[1..]; | |
| let bytes = output.lock().unwrap(); | |
| let actual_output = str::from_utf8(&bytes).unwrap(); | |
| println!("expected output:\n------\n{}------", expected_output); | |
| println!("actual output:\n------\n{}------", actual_output); | |
| assert!(expected_output == actual_output) | |
| } | |
| }) | |
| } | |
| fn make_span(file_text: &str, start: &Position, end: &Position) -> Span { | |
| let start = make_pos(file_text, start); | |
| let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends | |
| assert!(start <= end); | |
| Span::with_root_ctxt(BytePos(start as u32), BytePos(end as u32)) | |
| } | |
| fn make_pos(file_text: &str, pos: &Position) -> usize { | |
| let mut remainder = file_text; | |
| let mut offset = 0; | |
| for _ in 0..pos.count { | |
| if let Some(n) = remainder.find(&pos.string) { | |
| offset += n; | |
| remainder = &remainder[n + 1..]; | |
| } else { | |
| panic!("failed to find {} instances of {:?} in {:?}", pos.count, pos.string, file_text); | |
| } | |
| } | |
| offset | |
| } | |
| #[test] | |
| fn ends_on_col0() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "{", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "test", | |
| }], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:2:10 | |
| | | |
| 2 | fn foo() { | |
| | __________^ | |
| 3 | | } | |
| | |_^ test | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:2:10 | |
| │ | |
| 2 │ fn foo() { | |
| │ ┏━━━━━━━━━━┛ | |
| 3 │ ┃ } | |
| ╰╴┗━┛ test | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn ends_on_col2() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "{", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "test", | |
| }], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:2:10 | |
| | | |
| 2 | fn foo() { | |
| | __________^ | |
| ... | | |
| 5 | | } | |
| | |___^ test | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:2:10 | |
| │ | |
| 2 │ fn foo() { | |
| │ ┏━━━━━━━━━━┛ | |
| ‡ ┃ | |
| 5 │ ┃ } | |
| ╰╴┗━━━┛ test | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn non_nested() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 | |
| X1 Y1 | |
| X2 Y2 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Y2", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | X0 Y0 | |
| | ____^ - | |
| | | ______| | |
| 4 | || X1 Y1 | |
| 5 | || X2 Y2 | |
| | ||____^__- `Y` is a good letter too | |
| | |_____| | |
| | `X` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ X0 Y0 | |
| │ ┏━━━━┛ │ | |
| │ ┃┌──────┘ | |
| 4 │ ┃│ X1 Y1 | |
| 5 │ ┃│ X2 Y2 | |
| │ ┃└────╿──┘ `Y` is a good letter too | |
| │ ┗━━━━━┥ | |
| ╰╴ `X` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn nested() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 | |
| Y1 X1 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X1", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Y1", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | X0 Y0 | |
| | ____^ - | |
| | | ______| | |
| 4 | || Y1 X1 | |
| | ||____-__^ `X` is a good letter | |
| | |____| | |
| | `Y` is a good letter too | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ X0 Y0 | |
| │ ┏━━━━┛ │ | |
| │ ┃┌──────┘ | |
| 4 │ ┃│ Y1 X1 | |
| │ ┗│━━━━│━━┛ `X` is a good letter | |
| │ └────┤ | |
| ╰╴ `Y` is a good letter too | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiline_and_normal_overlap() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "Y0", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ___---^- | |
| | | | | |
| | | `Y` is a good letter too | |
| 4 | | X1 Y1 Z1 | |
| 5 | | X2 Y2 Z2 | |
| | |____^ `X` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━┬──┛─ | |
| │ ┃ │ | |
| │ ┃ `Y` is a good letter too | |
| 4 │ ┃ X1 Y1 Z1 | |
| 5 │ ┃ X2 Y2 Z2 | |
| ╰╴┗━━━━┛ `X` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_overlap() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Z1", count: 1 }, | |
| end: Position { string: "X3", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | _______^ | |
| 4 | | X1 Y1 Z1 | |
| | | _________- | |
| 5 | || X2 Y2 Z2 | |
| | ||____^ `X` is a good letter | |
| 6 | | X3 Y3 Z3 | |
| | |____- `Y` is a good letter too | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━━━━━┛ | |
| 4 │ ┃ X1 Y1 Z1 | |
| │ ┃┌─────────┘ | |
| 5 │ ┃│ X2 Y2 Z2 | |
| │ ┗│━━━━┛ `X` is a good letter | |
| 6 │ │ X3 Y3 Z3 | |
| ╰╴ └────┘ `Y` is a good letter too | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_1() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![(None, "bar")], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| = note: bar | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| │ | |
| ╰ note: bar | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_2() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![(None, "bar"), (None, "qux")], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| = note: bar | |
| = note: qux | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| │ | |
| ├ note: bar | |
| ╰ note: qux | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_3() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![(None, "bar"), (None, "baz"), (None, "qux")], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| = note: bar | |
| = note: baz | |
| = note: qux | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| │ | |
| ├ note: bar | |
| ├ note: baz | |
| ╰ note: qux | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_1() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "bar", | |
| )], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_2() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "bar", | |
| ), | |
| ( | |
| Some((Position { string: "X2", count: 1 }, Position { string: "Y2", count: 1 })), | |
| "qux", | |
| ), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| note: qux | |
| --> test.rs:5:3 | |
| | | |
| 5 | X2 Y2 Z2 | |
| | ^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| note: qux | |
| ╭▸ test.rs:5:3 | |
| │ | |
| 5 │ X2 Y2 Z2 | |
| ╰╴ ━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_3() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "bar", | |
| ), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "baz", | |
| ), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "qux", | |
| ), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| note: baz | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| note: qux | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| note: baz | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| note: qux | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_4() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "bar", | |
| ), | |
| (None, "qux"), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| = note: qux | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| │ ━━━━━━━━ | |
| ╰ note: qux | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_5() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| (None, "bar"), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "qux", | |
| ), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| = note: bar | |
| note: qux | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| │ | |
| ╰ note: bar | |
| note: qux | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_6() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| (None, "bar"), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "baz", | |
| ), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "qux", | |
| ), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| = note: bar | |
| note: baz | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| note: qux | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| │ | |
| ╰ note: bar | |
| note: baz | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| note: qux | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_7() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z3", count: 1 })), | |
| "bar", | |
| ), | |
| (None, "baz"), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "qux", | |
| ), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | / X1 Y1 Z1 | |
| 5 | | X2 Y2 Z2 | |
| 6 | | X3 Y3 Z3 | |
| | |__________^ | |
| = note: baz | |
| note: qux | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ ┏ X1 Y1 Z1 | |
| 5 │ ┃ X2 Y2 Z2 | |
| 6 │ ┃ X3 Y3 Z3 | |
| │ ┗━━━━━━━━━━┛ | |
| ╰ note: baz | |
| note: qux | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_8() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "bar", | |
| ), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "baz", | |
| ), | |
| (None, "qux"), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| note: baz | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| = note: qux | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| note: baz | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| │ ━━━━━━━━ | |
| ╰ note: qux | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_9() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| (None, "bar"), | |
| (None, "baz"), | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "qux", | |
| ), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| = note: bar | |
| = note: baz | |
| note: qux | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| │ | |
| ├ note: bar | |
| ╰ note: baz | |
| note: qux | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| ╰╴ ━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn different_note_spanned_10() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Z0", count: 1 }, | |
| label: "`X` is a good letter", | |
| }], | |
| vec![ | |
| ( | |
| Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), | |
| "bar", | |
| ), | |
| (None, "baz"), | |
| (None, "qux"), | |
| ], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | ^^^^^ `X` is a good letter | |
| | | |
| note: bar | |
| --> test.rs:4:3 | |
| | | |
| 4 | X1 Y1 Z1 | |
| | ^^^^^^^^ | |
| = note: baz | |
| = note: qux | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ━━━━━ `X` is a good letter | |
| ╰╴ | |
| note: bar | |
| ╭▸ test.rs:4:3 | |
| │ | |
| 4 │ X1 Y1 Z1 | |
| │ ━━━━━━━━ | |
| ├ note: baz | |
| ╰ note: qux | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn triple_overlap() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Y2", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Z0", count: 1 }, | |
| end: Position { string: "Z2", count: 1 }, | |
| label: "`Z` label", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | _____^ - - | |
| | | _______| | | |
| | || _________| | |
| 4 | ||| X1 Y1 Z1 | |
| 5 | ||| X2 Y2 Z2 | |
| | |||____^__-__- `Z` label | |
| | ||_____|__| | |
| | |______| `Y` is a good letter too | |
| | `X` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━━━┛ │ │ | |
| │ ┃┌───────┘ │ | |
| │ ┃│┌─────────┘ | |
| 4 │ ┃││ X1 Y1 Z1 | |
| 5 │ ┃││ X2 Y2 Z2 | |
| │ ┃│└────╿──│──┘ `Z` label | |
| │ ┃└─────│──┤ | |
| │ ┗━━━━━━┥ `Y` is a good letter too | |
| ╰╴ `X` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn triple_exact_overlap() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X2", count: 1 }, | |
| label: "`Z` label", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | / X0 Y0 Z0 | |
| 4 | | X1 Y1 Z1 | |
| 5 | | X2 Y2 Z2 | |
| | | ^ | |
| | | | | |
| | | `X` is a good letter | |
| | |____`Y` is a good letter too | |
| | `Z` label | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ ┏ X0 Y0 Z0 | |
| 4 │ ┃ X1 Y1 Z1 | |
| 5 │ ┃ X2 Y2 Z2 | |
| │ ┃ ╿ | |
| │ ┃ │ | |
| │ ┃ `X` is a good letter | |
| │ ┗━━━━`Y` is a good letter too | |
| ╰╴ `Z` label | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn minimum_depth() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "X1", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Y1", count: 1 }, | |
| end: Position { string: "Z2", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "X2", count: 1 }, | |
| end: Position { string: "Y3", count: 1 }, | |
| label: "`Z`", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | _______^ | |
| 4 | | X1 Y1 Z1 | |
| | | ____^_- | |
| | ||____| | |
| | | `X` is a good letter | |
| 5 | | X2 Y2 Z2 | |
| | |___-______- `Y` is a good letter too | |
| | ___| | |
| | | | |
| 6 | | X3 Y3 Z3 | |
| | |_______- `Z` | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━━━━━┛ | |
| 4 │ ┃ X1 Y1 Z1 | |
| │ ┃┌────╿─┘ | |
| │ ┗│━━━━┥ | |
| │ │ `X` is a good letter | |
| 5 │ │ X2 Y2 Z2 | |
| │ └───│──────┘ `Y` is a good letter too | |
| │ ┌───┘ | |
| │ │ | |
| 6 │ │ X3 Y3 Z3 | |
| ╰╴ └───────┘ `Z` | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn non_overlapping() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "X0", count: 1 }, | |
| end: Position { string: "X1", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Y2", count: 1 }, | |
| end: Position { string: "Z3", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | / X0 Y0 Z0 | |
| 4 | | X1 Y1 Z1 | |
| | |____^ `X` is a good letter | |
| 5 | X2 Y2 Z2 | |
| | ______- | |
| 6 | | X3 Y3 Z3 | |
| | |__________- `Y` is a good letter too | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ ┏ X0 Y0 Z0 | |
| 4 │ ┃ X1 Y1 Z1 | |
| │ ┗━━━━┛ `X` is a good letter | |
| 5 │ X2 Y2 Z2 | |
| │ ┌──────┘ | |
| 6 │ │ X3 Y3 Z3 | |
| ╰╴└──────────┘ `Y` is a good letter too | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn overlapping_start_and_end() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "X1", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Z1", count: 1 }, | |
| end: Position { string: "Z3", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | _______^ | |
| 4 | | X1 Y1 Z1 | |
| | | ____^____- | |
| | ||____| | |
| | | `X` is a good letter | |
| 5 | | X2 Y2 Z2 | |
| 6 | | X3 Y3 Z3 | |
| | |__________- `Y` is a good letter too | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━━━━━┛ | |
| 4 │ ┃ X1 Y1 Z1 | |
| │ ┃┌────╿────┘ | |
| │ ┗│━━━━┥ | |
| │ │ `X` is a good letter | |
| 5 │ │ X2 Y2 Z2 | |
| 6 │ │ X3 Y3 Z3 | |
| ╰╴ └──────────┘ `Y` is a good letter too | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_primary_without_message() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "`a` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "c", count: 1 }, | |
| end: Position { string: "c", count: 1 }, | |
| label: "", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:7 | |
| | | |
| 3 | a { b { c } d } | |
| | ----^^^^-^^-- `a` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:7 | |
| │ | |
| 3 │ a { b { c } d } | |
| ╰╴ ────━━━━─━━── `a` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiline_notes() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "`a` is a good letter", | |
| }], | |
| vec![(None, "foo\nbar"), (None, "foo\nbar")], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^^^^^^^^^^ `a` is a good letter | |
| | | |
| = note: foo | |
| bar | |
| = note: foo | |
| bar | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| │ ━━━━━━━━━━━━━ `a` is a good letter | |
| │ | |
| ├ note: foo | |
| │ bar | |
| ╰ note: foo | |
| bar | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_secondary_without_message() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "`a` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^-------^^ `a` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| ╰╴ ━━━━───────━━ `a` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_primary_without_message_2() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "`b` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "c", count: 1 }, | |
| end: Position { string: "c", count: 1 }, | |
| label: "", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:7 | |
| | | |
| 3 | a { b { c } d } | |
| | ----^^^^-^^-- | |
| | | | |
| | `b` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:7 | |
| │ | |
| 3 │ a { b { c } d } | |
| │ ────┯━━━─━━── | |
| │ │ | |
| ╰╴ `b` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_secondary_without_message_2() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "`b` is a good letter", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^-------^^ | |
| | | | |
| | `b` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| │ ━━━━┬──────━━ | |
| │ │ | |
| ╰╴ `b` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_secondary_without_message_3() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a bc d | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "b", count: 1 }, | |
| label: "`a` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "c", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a bc d | |
| | ^^^^---- | |
| | | | |
| | `a` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a bc d | |
| │ ┯━━━──── | |
| │ │ | |
| ╰╴ `a` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_without_message() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^-------^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| ╰╴ ━━━━───────━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_without_message_2() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "c", count: 1 }, | |
| end: Position { string: "c", count: 1 }, | |
| label: "", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:7 | |
| | | |
| 3 | a { b { c } d } | |
| | ----^^^^-^^-- | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:7 | |
| │ | |
| 3 │ a { b { c } d } | |
| ╰╴ ────━━━━─━━── | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn multiple_labels_with_message() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "`a` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "b", count: 1 }, | |
| end: Position { string: "}", count: 1 }, | |
| label: "`b` is a good letter", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^-------^^ | |
| | | | | |
| | | `b` is a good letter | |
| | `a` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| │ ┯━━━┬──────━━ | |
| │ │ │ | |
| │ │ `b` is a good letter | |
| ╰╴ `a` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn single_label_with_message() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "`a` is a good letter", | |
| }], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^^^^^^^^^^ `a` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| ╰╴ ━━━━━━━━━━━━━ `a` is a good letter | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn single_label_without_message() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| a { b { c } d } | |
| } | |
| "#, | |
| vec![SpanLabel { | |
| start: Position { string: "a", count: 1 }, | |
| end: Position { string: "d", count: 1 }, | |
| label: "", | |
| }], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:3 | |
| | | |
| 3 | a { b { c } d } | |
| | ^^^^^^^^^^^^^ | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:3 | |
| │ | |
| 3 │ a { b { c } d } | |
| ╰╴ ━━━━━━━━━━━━━ | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn long_snippet() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| X1 Y1 Z1 | |
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| X2 Y2 Z2 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "X1", count: 1 }, | |
| label: "`X` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Z1", count: 1 }, | |
| end: Position { string: "Z3", count: 1 }, | |
| label: "`Y` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | _______^ | |
| 4 | | X1 Y1 Z1 | |
| | | ____^____- | |
| | ||____| | |
| | | `X` is a good letter | |
| 5 | | 1 | |
| 6 | | 2 | |
| 7 | | 3 | |
| ... | | |
| 15 | | X2 Y2 Z2 | |
| 16 | | X3 Y3 Z3 | |
| | |__________- `Y` is a good letter too | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━━━━━┛ | |
| 4 │ ┃ X1 Y1 Z1 | |
| │ ┃┌────╿────┘ | |
| │ ┗│━━━━┥ | |
| │ │ `X` is a good letter | |
| 5 │ │ 1 | |
| 6 │ │ 2 | |
| 7 │ │ 3 | |
| ‡ │ | |
| 15 │ │ X2 Y2 Z2 | |
| 16 │ │ X3 Y3 Z3 | |
| ╰╴ └──────────┘ `Y` is a good letter too | |
| "#, | |
| ); | |
| } | |
| #[test] | |
| fn long_snippet_multiple_spans() { | |
| test_harness( | |
| r#" | |
| fn foo() { | |
| X0 Y0 Z0 | |
| 1 | |
| 2 | |
| 3 | |
| X1 Y1 Z1 | |
| 4 | |
| 5 | |
| 6 | |
| X2 Y2 Z2 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| X3 Y3 Z3 | |
| } | |
| "#, | |
| vec![ | |
| SpanLabel { | |
| start: Position { string: "Y0", count: 1 }, | |
| end: Position { string: "Y3", count: 1 }, | |
| label: "`Y` is a good letter", | |
| }, | |
| SpanLabel { | |
| start: Position { string: "Z1", count: 1 }, | |
| end: Position { string: "Z2", count: 1 }, | |
| label: "`Z` is a good letter too", | |
| }, | |
| ], | |
| vec![], | |
| r#" | |
| error: foo | |
| --> test.rs:3:6 | |
| | | |
| 3 | X0 Y0 Z0 | |
| | _______^ | |
| 4 | | 1 | |
| 5 | | 2 | |
| 6 | | 3 | |
| 7 | | X1 Y1 Z1 | |
| | | _________- | |
| 8 | || 4 | |
| 9 | || 5 | |
| 10 | || 6 | |
| 11 | || X2 Y2 Z2 | |
| | ||__________- `Z` is a good letter too | |
| ... | | |
| 15 | | 10 | |
| 16 | | X3 Y3 Z3 | |
| | |________^ `Y` is a good letter | |
| "#, | |
| r#" | |
| error: foo | |
| ╭▸ test.rs:3:6 | |
| │ | |
| 3 │ X0 Y0 Z0 | |
| │ ┏━━━━━━━┛ | |
| 4 │ ┃ 1 | |
| 5 │ ┃ 2 | |
| 6 │ ┃ 3 | |
| 7 │ ┃ X1 Y1 Z1 | |
| │ ┃┌─────────┘ | |
| 8 │ ┃│ 4 | |
| 9 │ ┃│ 5 | |
| 10 │ ┃│ 6 | |
| 11 │ ┃│ X2 Y2 Z2 | |
| │ ┃└──────────┘ `Z` is a good letter too | |
| ‡ ┃ | |
| 15 │ ┃ 10 | |
| 16 │ ┃ X3 Y3 Z3 | |
| ╰╴┗━━━━━━━━┛ `Y` is a good letter | |
| "#, | |
| ); | |
| } | |
| /// Parses an item. | |
| /// | |
| /// Returns `Ok(Some(item))` when successful, `Ok(None)` when no item was found, and `Err` | |
| /// when a syntax error occurred. | |
| fn parse_item_from_source_str( | |
| name: FileName, | |
| source: String, | |
| psess: &ParseSess, | |
| ) -> PResult<'_, Option<Box<ast::Item>>> { | |
| unwrap_or_emit_fatal(new_parser_from_source_str(psess, name, source, StripTokens::Nothing)) | |
| .parse_item(ForceCollect::No, AllowConstBlockItems::Yes) | |
| } | |
| // Produces a `rustc_span::span`. | |
| fn sp(a: u32, b: u32) -> Span { | |
| Span::with_root_ctxt(BytePos(a), BytePos(b)) | |
| } | |
| /// Parses a string, return an expression. | |
| fn string_to_expr(source_str: String) -> Box<ast::Expr> { | |
| with_error_checking_parse(source_str, &ParseSess::new(), |p| p.parse_expr()) | |
| } | |
| /// Parses a string, returns an item. | |
| fn string_to_item(source_str: String) -> Option<Box<ast::Item>> { | |
| with_error_checking_parse(source_str, &ParseSess::new(), |p| { | |
| p.parse_item(ForceCollect::No, AllowConstBlockItems::Yes) | |
| }) | |
| } | |
| #[test] | |
| fn bad_path_expr_1() { | |
| // This should trigger error: expected identifier, found keyword `return` | |
| create_default_session_globals_then(|| { | |
| with_expected_parse_error( | |
| "::abc::def::return", | |
| "expected identifier, found keyword `return`", | |
| |p| p.parse_expr(), | |
| ); | |
| }) | |
| } | |
| // Checks the token-tree-ization of macros. | |
| #[test] | |
| fn string_to_tts_macro() { | |
| create_default_session_globals_then(|| { | |
| let stream = string_to_stream("macro_rules! zip (($a)=>($a))".to_string()); | |
| let tts = &stream.iter().collect::<Vec<_>>()[..]; | |
| match tts { | |
| [ | |
| TokenTree::Token( | |
| Token { kind: token::Ident(name_macro_rules, IdentKind::Normal), .. }, | |
| _, | |
| ), | |
| TokenTree::Token(Token { kind: token::Bang, .. }, _), | |
| TokenTree::Token(Token { kind: token::Ident(name_zip, IdentKind::Normal), .. }, _), | |
| TokenTree::Delimited(.., macro_delim, macro_tts), | |
| ] if name_macro_rules == &kw::MacroRules && name_zip.as_str() == "zip" => { | |
| let tts = ¯o_tts.iter().collect::<Vec<_>>(); | |
| match &tts[..] { | |
| [ | |
| TokenTree::Delimited(.., first_delim, first_tts), | |
| TokenTree::Token(Token { kind: token::FatArrow, .. }, _), | |
| TokenTree::Delimited(.., second_delim, second_tts), | |
| ] if macro_delim == &Delimiter::Parenthesis => { | |
| let tts = &first_tts.iter().collect::<Vec<_>>(); | |
| match &tts[..] { | |
| [ | |
| TokenTree::Token(Token { kind: token::Dollar, .. }, _), | |
| TokenTree::Token( | |
| Token { kind: token::Ident(name, IdentKind::Normal), .. }, | |
| _, | |
| ), | |
| ] if first_delim == &Delimiter::Parenthesis && name.as_str() == "a" => { | |
| } | |
| _ => panic!("value 3: {:?} {:?}", first_delim, first_tts), | |
| } | |
| let tts = &second_tts.iter().collect::<Vec<_>>(); | |
| match &tts[..] { | |
| [ | |
| TokenTree::Token(Token { kind: token::Dollar, .. }, _), | |
| TokenTree::Token( | |
| Token { kind: token::Ident(name, IdentKind::Normal), .. }, | |
| _, | |
| ), | |
| ] if second_delim == &Delimiter::Parenthesis | |
| && name.as_str() == "a" => {} | |
| _ => panic!("value 4: {:?} {:?}", second_delim, second_tts), | |
| } | |
| } | |
| _ => panic!("value 2: {:?} {:?}", macro_delim, macro_tts), | |
| } | |
| } | |
| _ => panic!("value: {:?}", tts), | |
| } | |
| }) | |
| } | |
| #[test] | |
| fn string_to_tts_1() { | |
| create_default_session_globals_then(|| { | |
| let tts = string_to_stream("fn a(b: i32) { b; }".to_string()); | |
| let expected = TokenStream::new(vec![ | |
| TokenTree::token_alone(token::Ident(kw::Fn, IdentKind::Normal), sp(0, 2)), | |
| TokenTree::token_joint_hidden( | |
| token::Ident(sym::character('a'), IdentKind::Normal), | |
| sp(3, 4), | |
| ), | |
| TokenTree::Delimited( | |
| DelimSpan::from_pair(sp(4, 5), sp(11, 12)), | |
| // `JointHidden` because the `(` is followed immediately by | |
| // `b`, `Alone` because the `)` is followed by whitespace. | |
| DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), | |
| Delimiter::Parenthesis, | |
| TokenStream::new(vec![ | |
| TokenTree::token_joint( | |
| token::Ident(sym::character('b'), IdentKind::Normal), | |
| sp(5, 6), | |
| ), | |
| TokenTree::token_alone(token::Colon, sp(6, 7)), | |
| // `JointHidden` because the `i32` is immediately followed by the `)`. | |
| TokenTree::token_joint_hidden( | |
| token::Ident(sym::i32, IdentKind::Normal), | |
| sp(8, 11), | |
| ), | |
| ]), | |
| ), | |
| TokenTree::Delimited( | |
| DelimSpan::from_pair(sp(13, 14), sp(18, 19)), | |
| // First `Alone` because the `{` is followed by whitespace, | |
| // second `Alone` because the `}` is followed immediately by | |
| // EOF. | |
| DelimSpacing::new(Spacing::Alone, Spacing::Alone), | |
| Delimiter::Brace, | |
| TokenStream::new(vec![ | |
| TokenTree::token_joint( | |
| token::Ident(sym::character('b'), IdentKind::Normal), | |
| sp(15, 16), | |
| ), | |
| // `Alone` because the `;` is followed by whitespace. | |
| TokenTree::token_alone(token::Semi, sp(16, 17)), | |
| ]), | |
| ), | |
| ]); | |
| assert_eq!(tts, expected); | |
| }) | |
| } | |
| #[test] | |
| fn parse_use() { | |
| create_default_session_globals_then(|| { | |
| let use_s = "use foo::bar::baz;"; | |
| let vitem = string_to_item(use_s.to_string()).unwrap(); | |
| let vitem_s = item_to_string(&vitem); | |
| assert_eq!(&vitem_s[..], use_s); | |
| let use_s = "use foo::bar as baz;"; | |
| let vitem = string_to_item(use_s.to_string()).unwrap(); | |
| let vitem_s = item_to_string(&vitem); | |
| assert_eq!(&vitem_s[..], use_s); | |
| }) | |
| } | |
| #[test] | |
| fn parse_extern_crate() { | |
| create_default_session_globals_then(|| { | |
| let ex_s = "extern crate foo;"; | |
| let vitem = string_to_item(ex_s.to_string()).unwrap(); | |
| let vitem_s = item_to_string(&vitem); | |
| assert_eq!(&vitem_s[..], ex_s); | |
| let ex_s = "extern crate foo as bar;"; | |
| let vitem = string_to_item(ex_s.to_string()).unwrap(); | |
| let vitem_s = item_to_string(&vitem); | |
| assert_eq!(&vitem_s[..], ex_s); | |
| }) | |
| } | |
| fn get_spans_of_pat_idents(src: &str) -> Vec<Span> { | |
| let item = string_to_item(src.to_string()).unwrap(); | |
| struct PatIdentVisitor { | |
| spans: Vec<Span>, | |
| } | |
| impl<'a> visit::Visitor<'a> for PatIdentVisitor { | |
| fn visit_pat(&mut self, p: &'a ast::Pat) { | |
| match &p.kind { | |
| PatKind::Ident(_, ident, _) => { | |
| self.spans.push(ident.span); | |
| } | |
| _ => { | |
| visit::walk_pat(self, p); | |
| } | |
| } | |
| } | |
| } | |
| let mut v = PatIdentVisitor { spans: Vec::new() }; | |
| visit::walk_item(&mut v, &item); | |
| return v.spans; | |
| } | |
| #[test] | |
| fn span_of_self_arg_pat_idents_are_correct() { | |
| create_default_session_globals_then(|| { | |
| let srcs = [ | |
| "impl z { fn a (&self, &myarg: i32) {} }", | |
| "impl z { fn a (&mut self, &myarg: i32) {} }", | |
| "impl z { fn a (&'a self, &myarg: i32) {} }", | |
| "impl z { fn a (self, &myarg: i32) {} }", | |
| "impl z { fn a (self: Foo, &myarg: i32) {} }", | |
| ]; | |
| for src in srcs { | |
| let spans = get_spans_of_pat_idents(src); | |
| let (lo, hi) = (spans[0].lo(), spans[0].hi()); | |
| assert!( | |
| "self" == &src[lo.to_usize()..hi.to_usize()], | |
| "\"{}\" != \"self\". src=\"{}\"", | |
| &src[lo.to_usize()..hi.to_usize()], | |
| src | |
| ) | |
| } | |
| }) | |
| } | |
| #[test] | |
| fn parse_exprs() { | |
| create_default_session_globals_then(|| { | |
| // just make sure that they parse.... | |
| string_to_expr("3 + 4".to_string()); | |
| string_to_expr("a::z.froob(b,&(987+3))".to_string()); | |
| }) | |
| } | |
| #[test] | |
| fn attrs_fix_bug() { | |
| create_default_session_globals_then(|| { | |
| string_to_item( | |
| "pub fn mk_file_writer(path: &Path, flags: &[FileFlag]) | |
| -> Result<Box<Writer>, String> { | |
| #[cfg(windows)] | |
| fn wb() -> c_int { | |
| (O_WRONLY | libc::consts::os::extra::O_BINARY) as c_int | |
| } | |
| #[cfg(unix)] | |
| fn wb() -> c_int { O_WRONLY as c_int } | |
| let mut fflags: c_int = wb(); | |
| }" | |
| .to_string(), | |
| ); | |
| }) | |
| } | |
| #[test] | |
| fn crlf_doc_comments() { | |
| create_default_session_globals_then(|| { | |
| let psess = ParseSess::new(); | |
| let name_1 = FileName::Custom("crlf_source_1".to_string()); | |
| let source = "/// doc comment\r\nfn foo() {}".to_string(); | |
| let item = parse_item_from_source_str(name_1, source, &psess).unwrap().unwrap(); | |
| let doc = item.attrs.iter().filter_map(|at| at.doc_str()).next().unwrap(); | |
| assert_eq!(doc.as_str(), " doc comment"); | |
| let name_2 = FileName::Custom("crlf_source_2".to_string()); | |
| let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string(); | |
| let item = parse_item_from_source_str(name_2, source, &psess).unwrap().unwrap(); | |
| let docs = item.attrs.iter().filter_map(|at| at.doc_str()).collect::<Vec<_>>(); | |
| let b: &[_] = &[Symbol::intern(" doc comment"), Symbol::intern(" line 2")]; | |
| assert_eq!(&docs[..], b); | |
| let name_3 = FileName::Custom("clrf_source_3".to_string()); | |
| let source = "/** doc comment\r\n * with CRLF */\r\nfn foo() {}".to_string(); | |
| let item = parse_item_from_source_str(name_3, source, &psess).unwrap().unwrap(); | |
| let doc = item.attrs.iter().filter_map(|at| at.doc_str()).next().unwrap(); | |
| assert_eq!(doc.as_str(), " doc comment\n * with CRLF "); | |
| }); | |
| } | |
| #[test] | |
| fn ttdelim_span() { | |
| fn parse_expr_from_source_str( | |
| name: FileName, | |
| source: String, | |
| psess: &ParseSess, | |
| ) -> PResult<'_, Box<ast::Expr>> { | |
| unwrap_or_emit_fatal(new_parser_from_source_str(psess, name, source, StripTokens::Nothing)) | |
| .parse_expr() | |
| } | |
| create_default_session_globals_then(|| { | |
| let psess = ParseSess::new(); | |
| let expr = parse_expr_from_source_str( | |
| filename(psess.source_map(), "foo"), | |
| "foo!( fn main() { body } )".to_string(), | |
| &psess, | |
| ) | |
| .unwrap(); | |
| let ast::ExprKind::MacCall(mac) = &expr.kind else { panic!("not a macro") }; | |
| let span = mac.args.tokens.iter().last().unwrap().span(); | |
| match psess.source_map().span_to_snippet(span) { | |
| Ok(s) => assert_eq!(&s[..], "{ body }"), | |
| Err(_) => panic!("could not get snippet"), | |
| } | |
| }); | |
| } | |
| #[track_caller] | |
| fn look(p: &Parser<'_>, dist: usize, kind: rustc_ast::token::TokenKind) { | |
| // Do the `assert_eq` outside the closure so that `track_caller` works. | |
| // (`#![feature(closure_track_caller)]` + `#[track_caller]` on the closure | |
| // doesn't give the line number in the test below if the assertion fails.) | |
| let tok = p.look_ahead(dist, |tok| *tok); | |
| assert_eq!(kind, tok.kind); | |
| } | |
| #[test] | |
| fn look_ahead() { | |
| create_default_session_globals_then(|| { | |
| let sym_f = sym::character('f'); | |
| let sym_x = sym::character('x'); | |
| #[allow(non_snake_case)] | |
| let sym_S = sym::character('S'); | |
| let normal = IdentKind::Normal; | |
| let psess = ParseSess::new(); | |
| let mut p = string_to_parser(&psess, "fn f(x: u32) { x } struct S;".to_string()); | |
| // Current position is the `fn`. | |
| look(&p, 0, token::Ident(kw::Fn, normal)); | |
| look(&p, 1, token::Ident(sym_f, normal)); | |
| look(&p, 2, token::OpenParen); | |
| look(&p, 3, token::Ident(sym_x, normal)); | |
| look(&p, 4, token::Colon); | |
| look(&p, 5, token::Ident(sym::u32, normal)); | |
| look(&p, 6, token::CloseParen); | |
| look(&p, 7, token::OpenBrace); | |
| look(&p, 8, token::Ident(sym_x, normal)); | |
| look(&p, 9, token::CloseBrace); | |
| look(&p, 10, token::Ident(kw::Struct, normal)); | |
| look(&p, 11, token::Ident(sym_S, normal)); | |
| look(&p, 12, token::Semi); | |
| // Any lookahead past the end of the token stream returns `Eof`. | |
| look(&p, 13, token::Eof); | |
| look(&p, 14, token::Eof); | |
| look(&p, 15, token::Eof); | |
| look(&p, 100, token::Eof); | |
| // Move forward to the first `x`. | |
| for _ in 0..3 { | |
| p.bump(); | |
| } | |
| look(&p, 0, token::Ident(sym_x, normal)); | |
| look(&p, 1, token::Colon); | |
| look(&p, 2, token::Ident(sym::u32, normal)); | |
| look(&p, 3, token::CloseParen); | |
| look(&p, 4, token::OpenBrace); | |
| look(&p, 5, token::Ident(sym_x, normal)); | |
| look(&p, 6, token::CloseBrace); | |
| look(&p, 7, token::Ident(kw::Struct, normal)); | |
| look(&p, 8, token::Ident(sym_S, normal)); | |
| look(&p, 9, token::Semi); | |
| look(&p, 10, token::Eof); | |
| look(&p, 11, token::Eof); | |
| look(&p, 100, token::Eof); | |
| // Move forward to the `;`. | |
| for _ in 0..9 { | |
| p.bump(); | |
| } | |
| look(&p, 0, token::Semi); | |
| // Any lookahead past the end of the token stream returns `Eof`. | |
| look(&p, 1, token::Eof); | |
| look(&p, 100, token::Eof); | |
| // Move one past the `;`, i.e. past the end of the token stream. | |
| p.bump(); | |
| look(&p, 0, token::Eof); | |
| look(&p, 1, token::Eof); | |
| look(&p, 100, token::Eof); | |
| // Bumping after Eof is idempotent. | |
| p.bump(); | |
| look(&p, 0, token::Eof); | |
| look(&p, 1, token::Eof); | |
| look(&p, 100, token::Eof); | |
| }); | |
| } | |
| /// There used to be some buggy behaviour when using `look_ahead` not within | |
| /// the outermost token stream, which this test covers. | |
| #[test] | |
| fn look_ahead_non_outermost_stream() { | |
| create_default_session_globals_then(|| { | |
| let sym_f = sym::character('f'); | |
| let sym_x = sym::character('x'); | |
| #[allow(non_snake_case)] | |
| let sym_S = sym::character('S'); | |
| let normal = IdentKind::Normal; | |
| let psess = ParseSess::new(); | |
| let mut p = string_to_parser(&psess, "mod m { fn f(x: u32) { x } struct S; }".to_string()); | |
| // Move forward to the `fn`, which is not within the outermost token | |
| // stream (because it's inside the `mod { ... }`). | |
| for _ in 0..3 { | |
| p.bump(); | |
| } | |
| look(&p, 0, token::Ident(kw::Fn, normal)); | |
| look(&p, 1, token::Ident(sym_f, normal)); | |
| look(&p, 2, token::OpenParen); | |
| look(&p, 3, token::Ident(sym_x, normal)); | |
| look(&p, 4, token::Colon); | |
| look(&p, 5, token::Ident(sym::u32, normal)); | |
| look(&p, 6, token::CloseParen); | |
| look(&p, 7, token::OpenBrace); | |
| look(&p, 8, token::Ident(sym_x, normal)); | |
| look(&p, 9, token::CloseBrace); | |
| look(&p, 10, token::Ident(kw::Struct, normal)); | |
| look(&p, 11, token::Ident(sym_S, normal)); | |
| look(&p, 12, token::Semi); | |
| look(&p, 13, token::CloseBrace); | |
| // Any lookahead past the end of the token stream returns `Eof`. | |
| look(&p, 14, token::Eof); | |
| look(&p, 15, token::Eof); | |
| look(&p, 100, token::Eof); | |
| }); | |
| } | |
| #[test] | |
| fn debug_lookahead() { | |
| create_default_session_globals_then(|| { | |
| let psess = ParseSess::new(); | |
| let mut p = string_to_parser(&psess, "fn f(x: u32) { x } struct S;".to_string()); | |
| // Current position is the `fn`. | |
| assert_eq!( | |
| &format!("{:#?}", p.debug_lookahead(0)), | |
| "Parser { | |
| prev_token: Token { | |
| kind: Question, | |
| span: Span { | |
| lo: BytePos( | |
| 0, | |
| ), | |
| hi: BytePos( | |
| 0, | |
| ), | |
| ctxt: #0, | |
| }, | |
| }, | |
| tokens: [], | |
| approx_token_stream_pos: 0, | |
| .. | |
| }" | |
| ); | |
| assert_eq!( | |
| &format!("{:#?}", p.debug_lookahead(7)), | |
| "Parser { | |
| prev_token: Token { | |
| kind: Question, | |
| span: Span { | |
| lo: BytePos( | |
| 0, | |
| ), | |
| hi: BytePos( | |
| 0, | |
| ), | |
| ctxt: #0, | |
| }, | |
| }, | |
| tokens: [ | |
| Ident( | |
| \"fn\", | |
| Normal, | |
| ), | |
| Ident( | |
| \"f\", | |
| Normal, | |
| ), | |
| OpenParen, | |
| Ident( | |
| \"x\", | |
| Normal, | |
| ), | |
| Colon, | |
| Ident( | |
| \"u32\", | |
| Normal, | |
| ), | |
| CloseParen, | |
| ], | |
| approx_token_stream_pos: 0, | |
| .. | |
| }" | |
| ); | |
| // There are 13 tokens. We request 15, get 14; the last one is `Eof`. | |
| assert_eq!( | |
| &format!("{:#?}", p.debug_lookahead(15)), | |
| "Parser { | |
| prev_token: Token { | |
| kind: Question, | |
| span: Span { | |
| lo: BytePos( | |
| 0, | |
| ), | |
| hi: BytePos( | |
| 0, | |
| ), | |
| ctxt: #0, | |
| }, | |
| }, | |
| tokens: [ | |
| Ident( | |
| \"fn\", | |
| Normal, | |
| ), | |
| Ident( | |
| \"f\", | |
| Normal, | |
| ), | |
| OpenParen, | |
| Ident( | |
| \"x\", | |
| Normal, | |
| ), | |
| Colon, | |
| Ident( | |
| \"u32\", | |
| Normal, | |
| ), | |
| CloseParen, | |
| OpenBrace, | |
| Ident( | |
| \"x\", | |
| Normal, | |
| ), | |
| CloseBrace, | |
| Ident( | |
| \"struct\", | |
| Normal, | |
| ), | |
| Ident( | |
| \"S\", | |
| Normal, | |
| ), | |
| Semi, | |
| Eof, | |
| ], | |
| approx_token_stream_pos: 0, | |
| .. | |
| }" | |
| ); | |
| // Move forward to the second `x`. | |
| for _ in 0..8 { | |
| p.bump(); | |
| } | |
| assert_eq!( | |
| &format!("{:#?}", p.debug_lookahead(1)), | |
| "Parser { | |
| prev_token: Token { | |
| kind: OpenBrace, | |
| span: Span { | |
| lo: BytePos( | |
| 13, | |
| ), | |
| hi: BytePos( | |
| 14, | |
| ), | |
| ctxt: #0, | |
| }, | |
| }, | |
| tokens: [ | |
| Ident( | |
| \"x\", | |
| Normal, | |
| ), | |
| ], | |
| approx_token_stream_pos: 8, | |
| .. | |
| }" | |
| ); | |
| assert_eq!( | |
| &format!("{:#?}", p.debug_lookahead(4)), | |
| "Parser { | |
| prev_token: Token { | |
| kind: OpenBrace, | |
| span: Span { | |
| lo: BytePos( | |
| 13, | |
| ), | |
| hi: BytePos( | |
| 14, | |
| ), | |
| ctxt: #0, | |
| }, | |
| }, | |
| tokens: [ | |
| Ident( | |
| \"x\", | |
| Normal, | |
| ), | |
| CloseBrace, | |
| Ident( | |
| \"struct\", | |
| Normal, | |
| ), | |
| Ident( | |
| \"S\", | |
| Normal, | |
| ), | |
| ], | |
| approx_token_stream_pos: 8, | |
| .. | |
| }" | |
| ); | |
| // Move two past the final token (the `;`). | |
| for _ in 0..6 { | |
| p.bump(); | |
| } | |
| assert_eq!( | |
| &format!("{:#?}", p.debug_lookahead(3)), | |
| "Parser { | |
| prev_token: Token { | |
| kind: Eof, | |
| span: Span { | |
| lo: BytePos( | |
| 27, | |
| ), | |
| hi: BytePos( | |
| 28, | |
| ), | |
| ctxt: #0, | |
| }, | |
| }, | |
| tokens: [ | |
| Eof, | |
| ], | |
| approx_token_stream_pos: 14, | |
| .. | |
| }" | |
| ); | |
| }); | |
| } | |
| // This tests that when parsing a string (rather than a file) we don't try | |
| // and read in a file for a module declaration and just parse a stub. | |
| // See `recurse_into_file_modules` in the parser. | |
| #[test] | |
| fn out_of_line_mod() { | |
| create_default_session_globals_then(|| { | |
| let psess = ParseSess::new(); | |
| let item = parse_item_from_source_str( | |
| filename(psess.source_map(), "foo"), | |
| "mod foo { struct S; mod this_does_not_exist; }".to_owned(), | |
| &psess, | |
| ) | |
| .unwrap() | |
| .unwrap(); | |
| let ast::ItemKind::Mod(_, _, mod_kind) = &item.kind else { panic!() }; | |
| assert_matches!(mod_kind, ast::ModKind::Loaded(items, ..) if items.len() == 2); | |
| }); | |
| } | |
| #[test] | |
| fn eqmodws() { | |
| assert_eq!(matches_codepattern("", ""), true); | |
| assert_eq!(matches_codepattern("", "a"), false); | |
| assert_eq!(matches_codepattern("a", ""), false); | |
| assert_eq!(matches_codepattern("a", "a"), true); | |
| assert_eq!(matches_codepattern("a b", "a \n\t\r b"), true); | |
| assert_eq!(matches_codepattern("a b ", "a \n\t\r b"), true); | |
| assert_eq!(matches_codepattern("a b", "a \n\t\r b "), false); | |
| assert_eq!(matches_codepattern("a b", "a b"), true); | |
| assert_eq!(matches_codepattern("ab", "a b"), false); | |
| assert_eq!(matches_codepattern("a b", "ab"), true); | |
| assert_eq!(matches_codepattern(" a b", "ab"), true); | |
| } | |
| #[test] | |
| fn pattern_whitespace() { | |
| assert_eq!(matches_codepattern("", "\x0C"), false); | |
| assert_eq!(matches_codepattern("a b ", "a \u{0085}\n\t\r b"), true); | |
| assert_eq!(matches_codepattern("a b", "a \u{0085}\n\t\r b "), false); | |
| } | |
| #[test] | |
| fn non_pattern_whitespace() { | |
| // These have the property 'White_Space' but not 'Pattern_White_Space' | |
| assert_eq!(matches_codepattern("a b", "a\u{2002}b"), false); | |
| assert_eq!(matches_codepattern("a b", "a\u{2002}b"), false); | |
| assert_eq!(matches_codepattern("\u{205F}a b", "ab"), false); | |
| assert_eq!(matches_codepattern("a \u{3000}b", "ab"), false); | |
| } |