2959 lines
67 KiB
Rust
2959 lines
67 KiB
Rust
#![allow(rustc::symbol_intern_string_literal)]
|
|
|
|
use std::assert_matches::assert_matches;
|
|
use std::io::prelude::*;
|
|
use std::iter::Peekable;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::{io, str};
|
|
|
|
use ast::token::IdentIsRaw;
|
|
use rustc_ast::ptr::P;
|
|
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::emitter::{HumanEmitter, OutputTheme};
|
|
use rustc_errors::{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 termcolor::WriteColor;
|
|
|
|
use crate::parser::{ForceCollect, Parser};
|
|
use crate::{new_parser_from_source_str, source_str_to_stream, unwrap_or_emit_fatal};
|
|
|
|
fn psess() -> ParseSess {
|
|
ParseSess::new(vec![crate::DEFAULT_LOCALE_RESOURCE])
|
|
}
|
|
|
|
/// 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,
|
|
PathBuf::from("bogofile").into(),
|
|
source_str,
|
|
))
|
|
}
|
|
|
|
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 fallback_bundle =
|
|
rustc_errors::fallback_fluent_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
|
|
let mut emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle)
|
|
.sm(Some(source_map.clone()))
|
|
.diagnostic_width(Some(140));
|
|
emitter = emitter.theme(theme);
|
|
let dcx = DiagCtxt::new(Box::new(emitter));
|
|
(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 = psess();
|
|
unwrap_or_emit_fatal(source_str_to_stream(
|
|
&psess,
|
|
PathBuf::from("bogofile").into(),
|
|
source_str,
|
|
None,
|
|
))
|
|
}
|
|
|
|
/// Parses a string, returns a crate.
|
|
pub(crate) fn string_to_crate(source_str: String) -> ast::Crate {
|
|
let psess = psess();
|
|
with_error_checking_parse(source_str, &psess, |p| p.parse_crate_mod())
|
|
}
|
|
|
|
/// 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> WriteColor for Shared<T> {
|
|
fn supports_color(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn reset(&mut self) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
#[allow(rustc::untranslatable_diagnostic)] // no translation needed for tests
|
|
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(Path::new("test.rs").to_owned().into(), 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<P<ast::Item>>> {
|
|
unwrap_or_emit_fatal(new_parser_from_source_str(psess, name, source))
|
|
.parse_item(ForceCollect::No)
|
|
}
|
|
|
|
// 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) -> P<ast::Expr> {
|
|
with_error_checking_parse(source_str, &psess(), |p| p.parse_expr())
|
|
}
|
|
|
|
/// Parses a string, returns an item.
|
|
fn string_to_item(source_str: String) -> Option<P<ast::Item>> {
|
|
with_error_checking_parse(source_str, &psess(), |p| p.parse_item(ForceCollect::No))
|
|
}
|
|
|
|
#[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, IdentIsRaw::No), .. },
|
|
_,
|
|
),
|
|
TokenTree::Token(Token { kind: token::Bang, .. }, _),
|
|
TokenTree::Token(Token { kind: token::Ident(name_zip, IdentIsRaw::No), .. }, _),
|
|
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, IdentIsRaw::No), .. },
|
|
_,
|
|
),
|
|
] 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, IdentIsRaw::No), .. },
|
|
_,
|
|
),
|
|
] 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, IdentIsRaw::No), sp(0, 2)),
|
|
TokenTree::token_joint_hidden(
|
|
token::Ident(Symbol::intern("a"), IdentIsRaw::No),
|
|
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(Symbol::intern("b"), IdentIsRaw::No),
|
|
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, IdentIsRaw::No),
|
|
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(Symbol::intern("b"), IdentIsRaw::No),
|
|
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 = psess();
|
|
|
|
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<'_, P<ast::Expr>> {
|
|
unwrap_or_emit_fatal(new_parser_from_source_str(psess, name, source)).parse_expr()
|
|
}
|
|
|
|
create_default_session_globals_then(|| {
|
|
let psess = psess();
|
|
let expr = parse_expr_from_source_str(
|
|
PathBuf::from("foo").into(),
|
|
"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 = Symbol::intern("f");
|
|
let sym_x = Symbol::intern("x");
|
|
#[allow(non_snake_case)]
|
|
let sym_S = Symbol::intern("S");
|
|
let raw_no = IdentIsRaw::No;
|
|
|
|
let psess = psess();
|
|
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, raw_no));
|
|
look(&p, 1, token::Ident(sym_f, raw_no));
|
|
look(&p, 2, token::OpenDelim(Delimiter::Parenthesis));
|
|
look(&p, 3, token::Ident(sym_x, raw_no));
|
|
look(&p, 4, token::Colon);
|
|
look(&p, 5, token::Ident(sym::u32, raw_no));
|
|
look(&p, 6, token::CloseDelim(Delimiter::Parenthesis));
|
|
look(&p, 7, token::OpenDelim(Delimiter::Brace));
|
|
look(&p, 8, token::Ident(sym_x, raw_no));
|
|
look(&p, 9, token::CloseDelim(Delimiter::Brace));
|
|
look(&p, 10, token::Ident(kw::Struct, raw_no));
|
|
look(&p, 11, token::Ident(sym_S, raw_no));
|
|
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, raw_no));
|
|
look(&p, 1, token::Colon);
|
|
look(&p, 2, token::Ident(sym::u32, raw_no));
|
|
look(&p, 3, token::CloseDelim(Delimiter::Parenthesis));
|
|
look(&p, 4, token::OpenDelim(Delimiter::Brace));
|
|
look(&p, 5, token::Ident(sym_x, raw_no));
|
|
look(&p, 6, token::CloseDelim(Delimiter::Brace));
|
|
look(&p, 7, token::Ident(kw::Struct, raw_no));
|
|
look(&p, 8, token::Ident(sym_S, raw_no));
|
|
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 = Symbol::intern("f");
|
|
let sym_x = Symbol::intern("x");
|
|
#[allow(non_snake_case)]
|
|
let sym_S = Symbol::intern("S");
|
|
let raw_no = IdentIsRaw::No;
|
|
|
|
let psess = psess();
|
|
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, raw_no));
|
|
look(&p, 1, token::Ident(sym_f, raw_no));
|
|
look(&p, 2, token::OpenDelim(Delimiter::Parenthesis));
|
|
look(&p, 3, token::Ident(sym_x, raw_no));
|
|
look(&p, 4, token::Colon);
|
|
look(&p, 5, token::Ident(sym::u32, raw_no));
|
|
look(&p, 6, token::CloseDelim(Delimiter::Parenthesis));
|
|
look(&p, 7, token::OpenDelim(Delimiter::Brace));
|
|
look(&p, 8, token::Ident(sym_x, raw_no));
|
|
look(&p, 9, token::CloseDelim(Delimiter::Brace));
|
|
look(&p, 10, token::Ident(kw::Struct, raw_no));
|
|
look(&p, 11, token::Ident(sym_S, raw_no));
|
|
look(&p, 12, token::Semi);
|
|
look(&p, 13, token::CloseDelim(Delimiter::Brace));
|
|
// 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);
|
|
});
|
|
}
|
|
|
|
// FIXME(nnethercote) All the output is currently wrong.
|
|
#[test]
|
|
fn debug_lookahead() {
|
|
create_default_session_globals_then(|| {
|
|
let psess = psess();
|
|
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\",
|
|
No,
|
|
),
|
|
Ident(
|
|
\"f\",
|
|
No,
|
|
),
|
|
OpenDelim(
|
|
Parenthesis,
|
|
),
|
|
Ident(
|
|
\"x\",
|
|
No,
|
|
),
|
|
Colon,
|
|
Ident(
|
|
\"u32\",
|
|
No,
|
|
),
|
|
CloseDelim(
|
|
Parenthesis,
|
|
),
|
|
],
|
|
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\",
|
|
No,
|
|
),
|
|
Ident(
|
|
\"f\",
|
|
No,
|
|
),
|
|
OpenDelim(
|
|
Parenthesis,
|
|
),
|
|
Ident(
|
|
\"x\",
|
|
No,
|
|
),
|
|
Colon,
|
|
Ident(
|
|
\"u32\",
|
|
No,
|
|
),
|
|
CloseDelim(
|
|
Parenthesis,
|
|
),
|
|
OpenDelim(
|
|
Brace,
|
|
),
|
|
Ident(
|
|
\"x\",
|
|
No,
|
|
),
|
|
CloseDelim(
|
|
Brace,
|
|
),
|
|
Ident(
|
|
\"struct\",
|
|
No,
|
|
),
|
|
Ident(
|
|
\"S\",
|
|
No,
|
|
),
|
|
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: OpenDelim(
|
|
Brace,
|
|
),
|
|
span: Span {
|
|
lo: BytePos(
|
|
13,
|
|
),
|
|
hi: BytePos(
|
|
14,
|
|
),
|
|
ctxt: #0,
|
|
},
|
|
},
|
|
tokens: [
|
|
Ident(
|
|
\"x\",
|
|
No,
|
|
),
|
|
],
|
|
approx_token_stream_pos: 8,
|
|
..
|
|
}"
|
|
);
|
|
assert_eq!(
|
|
&format!("{:#?}", p.debug_lookahead(4)),
|
|
"Parser {
|
|
prev_token: Token {
|
|
kind: OpenDelim(
|
|
Brace,
|
|
),
|
|
span: Span {
|
|
lo: BytePos(
|
|
13,
|
|
),
|
|
hi: BytePos(
|
|
14,
|
|
),
|
|
ctxt: #0,
|
|
},
|
|
},
|
|
tokens: [
|
|
Ident(
|
|
\"x\",
|
|
No,
|
|
),
|
|
CloseDelim(
|
|
Brace,
|
|
),
|
|
Ident(
|
|
\"struct\",
|
|
No,
|
|
),
|
|
Ident(
|
|
\"S\",
|
|
No,
|
|
),
|
|
],
|
|
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 item = parse_item_from_source_str(
|
|
PathBuf::from("foo").into(),
|
|
"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);
|
|
}
|