Rollup merge of #148505 - cyrgani:pm-tests, r=madsmtm

add larger test for `proc_macro` `FromStr` implementations

Currently, there are only few tests that check the output of `TokenStream::from_str` and `Literal::from_str` (which is somewhat understandable as the rustc implementation just delegates these calls to the parser). In preparation for both the standalone backend (rust-lang/rust#130856) which will probably need to reimplement this logic as well as for removing panics from these functions (rust-lang/rust#58736), this PR adds a test which shows the various messy ways of how these functions report errors and the return values for successful parses.
Followup PRs such as rust-lang/rust#147859 will change more and more of these "diagnostic + error"s into `LexErrors`.

The test structure with the extra module is used to allow reusing it later easily for the standalone backend.
This commit is contained in:
Stuart Cook
2025-11-17 16:41:00 +11:00
committed by GitHub
5 changed files with 424 additions and 0 deletions
@@ -0,0 +1,143 @@
use std::fmt::Debug;
use std::panic::catch_unwind;
use std::str::FromStr;
use proc_macro::*;
use self::Mode::*;
// FIXME: all cases should become `NormalOk` or `NormalErr`
#[derive(PartialEq, Clone, Copy)]
enum Mode {
NormalOk,
NormalErr,
OtherError,
OtherWithPanic,
}
fn parse<T>(s: &str, mode: Mode)
where
T: FromStr<Err = LexError> + Debug,
{
match mode {
NormalOk => {
let t = T::from_str(s);
println!("{:?}", t);
assert!(t.is_ok());
}
NormalErr => {
let t = T::from_str(s);
println!("{:?}", t);
assert!(t.is_err());
}
OtherError => {
println!("{:?}", T::from_str(s));
}
OtherWithPanic => {
if catch_unwind(|| println!("{:?}", T::from_str(s))).is_ok() {
eprintln!("{s} did not panic");
}
}
}
}
fn stream(s: &str, mode: Mode) {
parse::<TokenStream>(s, mode);
}
fn lit(s: &str, mode: Mode) {
parse::<Literal>(s, mode);
if mode == NormalOk {
let Ok(lit) = Literal::from_str(s) else {
panic!("literal was not ok");
};
let Ok(stream) = TokenStream::from_str(s) else {
panic!("tokenstream was not ok, but literal was");
};
let Some(tree) = stream.into_iter().next() else {
panic!("tokenstream should have a tokentree");
};
if let TokenTree::Literal(tokenstream_lit) = tree {
assert_eq!(lit.to_string(), tokenstream_lit.to_string());
}
}
}
pub fn run() {
// returns Ok(valid instance)
lit("123", NormalOk);
lit("\"ab\"", NormalOk);
lit("\'b\'", NormalOk);
lit("'b'", NormalOk);
lit("b\"b\"", NormalOk);
lit("c\"b\"", NormalOk);
lit("cr\"b\"", NormalOk);
lit("b'b'", NormalOk);
lit("256u8", NormalOk);
lit("-256u8", NormalOk);
stream("-256u8", NormalOk);
lit("0b11111000000001111i16", NormalOk);
lit("0xf32", NormalOk);
lit("0b0f32", NormalOk);
lit("2E4", NormalOk);
lit("2.2E-4f64", NormalOk);
lit("18u8E", NormalOk);
lit("18.0u8E", NormalOk);
lit("cr#\"// /* // \n */\"#", NormalOk);
lit("'\\''", NormalOk);
lit("'\\\''", NormalOk);
lit(&format!("r{0}\"a\"{0}", "#".repeat(255)), NormalOk);
stream("fn main() { println!(\"Hello, world!\") }", NormalOk);
stream("18.u8E", NormalOk);
stream("18.0f32", NormalOk);
stream("18.0f34", NormalOk);
stream("18.bu8", NormalOk);
stream("3//\n4", NormalOk);
stream(
"\'c\'/*\n
*/",
NormalOk,
);
stream("/*a*/ //", NormalOk);
println!("### ERRORS");
// returns Err(LexError)
lit("\'c\'/**/", NormalErr);
lit(" 0", NormalErr);
lit("0 ", NormalErr);
lit("0//", NormalErr);
lit("3//\n4", NormalErr);
lit("18.u8E", NormalErr);
lit("/*a*/ //", NormalErr);
// FIXME: all of the cases below should return an Err and emit no diagnostics, but don't yet.
// emits diagnostics and returns LexError
lit("r'r'", OtherError);
lit("c'r'", OtherError);
// emits diagnostic and returns a seemingly valid tokenstream
stream("r'r'", OtherError);
stream("c'r'", OtherError);
for parse in [stream as fn(&str, Mode), lit] {
// emits diagnostic(s), then panics
parse("1 ) 2", OtherWithPanic);
parse("( x [ ) ]", OtherWithPanic);
parse("r#", OtherWithPanic);
// emits diagnostic(s), then returns Ok(Literal { kind: ErrWithGuar, .. })
parse("0b2", OtherError);
parse("0bf32", OtherError);
parse("0b0.0f32", OtherError);
parse("'\''", OtherError);
parse(
"'
'", OtherError,
);
parse(&format!("r{0}\"a\"{0}", "#".repeat(256)), OtherWithPanic);
// emits diagnostic, then, when parsing as a lit, returns LexError, otherwise ErrWithGuar
parse("/*a*/ 0b2 //", OtherError);
}
}
@@ -0,0 +1,11 @@
extern crate proc_macro;
use proc_macro::*;
#[path = "nonfatal-parsing-body.rs"]
mod body;
#[proc_macro]
pub fn run(_: TokenStream) -> TokenStream {
body::run();
TokenStream::new()
}
+19
View File
@@ -0,0 +1,19 @@
//@ proc-macro: nonfatal-parsing.rs
//@ needs-unwind
//@ edition: 2024
//@ dont-require-annotations: ERROR
//@ ignore-backends: gcc
// FIXME: should be a run-pass test once invalidly parsed tokens no longer result in diagnostics
extern crate proc_macro;
extern crate nonfatal_parsing;
#[path = "auxiliary/nonfatal-parsing-body.rs"]
mod body;
fn main() {
nonfatal_parsing::run!();
// FIXME: enable this once the standalone backend exists
// https://github.com/rust-lang/rust/issues/130856
// body::run();
}
+197
View File
@@ -0,0 +1,197 @@
error: prefix `r` is unknown
--> <proc-macro source code>:1:1
|
LL | r'r'
| ^ unknown prefix
|
= note: prefixed identifiers and literals are reserved since Rust 2021
help: consider inserting whitespace here
|
LL | r 'r'
| +
error: prefix `c` is unknown
--> <proc-macro source code>:1:1
|
LL | c'r'
| ^ unknown prefix
|
= note: prefixed identifiers and literals are reserved since Rust 2021
help: consider inserting whitespace here
|
LL | c 'r'
| +
error: unexpected closing delimiter: `)`
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^ unexpected closing delimiter
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected closing delimiter: `]`
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| -^^^^^^^^^^^^^^^^^^^^^^^
| |
| the nearest open delimiter
| missing open `(` for this delimiter
| unexpected closing delimiter
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: found invalid character; only `#` is allowed in raw string delimitation: \u{0}
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid digit for a base 2 literal
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0768]: no valid digits found for number
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: binary float literal is not supported
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: character constant must be escaped: `'`
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
help: escape the character
|
LL - nonfatal_parsing::run!();
LL + nonfatal_parsing::run!(\';
|
error: character constant must be escaped: `\n`
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
help: escape the character
|
LL - nonfatal_parsing::run!();
LL + nonfatal_parsing::run!(\n;
|
error: too many `#` symbols: raw strings may be delimited by up to 255 `#` symbols, but found 256
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid digit for a base 2 literal
--> $DIR/nonfatal-parsing.rs:15:5
|
LL | nonfatal_parsing::run!();
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
= note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected closing delimiter: `)`
--> <proc-macro source code>:1:3
|
LL | 1 ) 2
| ^ unexpected closing delimiter
error: unexpected closing delimiter: `]`
--> <proc-macro source code>:1:10
|
LL | ( x [ ) ]
| - - ^ unexpected closing delimiter
| | |
| | missing open `(` for this delimiter
| the nearest open delimiter
error: found invalid character; only `#` is allowed in raw string delimitation: \u{0}
--> <proc-macro source code>:1:1
|
LL | r#
| ^^
error: invalid digit for a base 2 literal
--> <proc-macro source code>:1:3
|
LL | 0b2
| ^
error[E0768]: no valid digits found for number
--> <proc-macro source code>:1:1
|
LL | 0bf32
| ^^
error: binary float literal is not supported
--> <proc-macro source code>:1:1
|
LL | 0b0.0f32
| ^^^^^
error: character constant must be escaped: `'`
--> <proc-macro source code>:1:2
|
LL | '''
| ^
|
help: escape the character
|
LL | '\''
| +
error: character constant must be escaped: `\n`
--> <proc-macro source code>:1:2
|
LL | '
| __^
LL | | '
| |_^
|
help: escape the character
|
LL | '\n'
| ++
error: too many `#` symbols: raw strings may be delimited by up to 255 `#` symbols, but found 256
--> <proc-macro source code>:1:1
|
LL | r#######################################...##################################################
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: invalid digit for a base 2 literal
--> <proc-macro source code>:1:9
|
LL | /*a*/ 0b2 //
| ^
error: aborting due to 22 previous errors
For more information about this error, try `rustc --explain E0768`.
@@ -0,0 +1,54 @@
Ok(Literal { kind: Integer, symbol: "123", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Str, symbol: "ab", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Char, symbol: "b", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Char, symbol: "b", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: ByteStr, symbol: "b", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: CStr, symbol: "b", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: CStrRaw(0), symbol: "b", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Byte, symbol: "b", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Integer, symbol: "256", suffix: Some("u8"), span: #44 bytes(361..385) })
Ok(Literal { kind: Integer, symbol: "-256", suffix: Some("u8"), span: #44 bytes(361..385) })
Ok(TokenStream [Punct { ch: '-', spacing: Alone, span: #44 bytes(361..385) }, Literal { kind: Integer, symbol: "256", suffix: Some("u8"), span: #44 bytes(361..385) }])
Ok(Literal { kind: Integer, symbol: "0b11111000000001111", suffix: Some("i16"), span: #44 bytes(361..385) })
Ok(Literal { kind: Integer, symbol: "0xf32", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Integer, symbol: "0b0", suffix: Some("f32"), span: #44 bytes(361..385) })
Ok(Literal { kind: Float, symbol: "2E4", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Float, symbol: "2.2E-4", suffix: Some("f64"), span: #44 bytes(361..385) })
Ok(Literal { kind: Integer, symbol: "18", suffix: Some("u8E"), span: #44 bytes(361..385) })
Ok(Literal { kind: Float, symbol: "18.0", suffix: Some("u8E"), span: #44 bytes(361..385) })
Ok(Literal { kind: CStrRaw(1), symbol: "// /* // \n */", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Char, symbol: "\'", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: Char, symbol: "\'", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: StrRaw(255), symbol: "a", suffix: None, span: #44 bytes(361..385) })
Ok(TokenStream [Ident { ident: "fn", span: #44 bytes(361..385) }, Ident { ident: "main", span: #44 bytes(361..385) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #44 bytes(361..385) }, Group { delimiter: Brace, stream: TokenStream [Ident { ident: "println", span: #44 bytes(361..385) }, Punct { ch: '!', spacing: Alone, span: #44 bytes(361..385) }, Group { delimiter: Parenthesis, stream: TokenStream [Literal { kind: Str, symbol: "Hello, world!", suffix: None, span: #44 bytes(361..385) }], span: #44 bytes(361..385) }], span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: Integer, symbol: "18", suffix: None, span: #44 bytes(361..385) }, Punct { ch: '.', spacing: Alone, span: #44 bytes(361..385) }, Ident { ident: "u8E", span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: Float, symbol: "18.0", suffix: Some("f32"), span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: Float, symbol: "18.0", suffix: Some("f34"), span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: Integer, symbol: "18", suffix: None, span: #44 bytes(361..385) }, Punct { ch: '.', spacing: Alone, span: #44 bytes(361..385) }, Ident { ident: "bu8", span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: Integer, symbol: "3", suffix: None, span: #44 bytes(361..385) }, Literal { kind: Integer, symbol: "4", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: Char, symbol: "c", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [])
### ERRORS
Err(LexError)
Err(LexError)
Err(LexError)
Err(LexError)
Err(LexError)
Err(LexError)
Err(LexError)
Err(LexError)
Err(LexError)
Ok(TokenStream [Ident { ident: "r", span: #44 bytes(361..385) }, Literal { kind: Char, symbol: "r", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [Ident { ident: "c", span: #44 bytes(361..385) }, Literal { kind: Char, symbol: "r", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b", suffix: Some("f32"), span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b0.0", suffix: Some("f32"), span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "'''", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "'\n'", suffix: None, span: #44 bytes(361..385) }])
Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: #44 bytes(361..385) }])
Ok(Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: ErrWithGuar, symbol: "0b", suffix: Some("f32"), span: #44 bytes(361..385) })
Ok(Literal { kind: ErrWithGuar, symbol: "0b0.0", suffix: Some("f32"), span: #44 bytes(361..385) })
Ok(Literal { kind: ErrWithGuar, symbol: "'''", suffix: None, span: #44 bytes(361..385) })
Ok(Literal { kind: ErrWithGuar, symbol: "'\n'", suffix: None, span: #44 bytes(361..385) })
Err(LexError)