99#![ cfg_attr( docsrs, feature( doc_auto_cfg) ) ]
1010#![ deny( rustdoc:: all) ]
1111
12+ use std:: num:: ParseIntError ;
13+ use std:: str:: FromStr ;
14+
1215#[ cfg( doc) ]
1316use proc_macro2:: { Punct , Spacing } ;
1417
@@ -29,6 +32,19 @@ mod assert;
2932#[ doc( hidden) ]
3033pub mod __private;
3134
35+ mod sealed {
36+ pub trait Sealed { }
37+
38+ macro_rules! sealed {
39+ [ $( $ty: ident) ,* $( , ) ?] => { $(
40+ impl Sealed for proc_macro:: $ty { }
41+ impl Sealed for proc_macro2:: $ty { }
42+ ) * } ;
43+ }
44+
45+ sealed ! [ TokenStream , TokenTree , Punct , Literal , Group ] ;
46+ }
47+
3248macro_rules! once {
3349 ( ( $( $tts: tt) * ) $( $tail: tt) * ) => {
3450 $( $tts) *
@@ -44,7 +60,7 @@ macro_rules! attr {
4460macro_rules! trait_def {
4561 ( $item_attr: tt, $trait: ident, $( $fn_attr: tt, $fn: ident, $( { $( $gen: tt) * } ) ?, $args: tt, $( $ret: ty) ?) ,* ) => {
4662 attr!( $item_attr,
47- pub trait $trait {
63+ pub trait $trait: crate :: sealed :: Sealed {
4864 $( attr!( $fn_attr, fn $fn $( $( $gen) * ) ? $args $( -> $ret) ?; ) ; ) *
4965 } ) ;
5066 } ;
@@ -63,19 +79,19 @@ macro_rules! impl_via_trait {
6379 $( #$trait_attr: tt) *
6480 impl $trait: ident for $type: ident {
6581 $( $( #$fn_attr: tt) *
66- fn $fn: ident $args: tt $( -> $ret: ty) ? { $( $stmts: tt) * } ) *
82+ fn $fn: ident $( { $ ( $gen : tt ) * } ) ? ( $ ( $ args: tt) * ) $( -> $ret: ty) ? { $( $stmts: tt) * } ) *
6783 }
6884 ) +) => {
69- once!( $( ( trait_def!( ( $( $trait_attr) * ) , $trait, $( ( $( $fn_attr) * ) , $fn, , $ args, $( $ret) ?) ,* ) ; ) ) +) ;
85+ once!( $( ( trait_def!( ( $( $trait_attr) * ) , $trait, $( ( $( $fn_attr) * ) , $fn, $ ( { $ ( $gen ) * } ) ? , ( $ ( $ args) * ) , $( $ret) ?) ,* ) ; ) ) +) ;
7086 #[ cfg( feature = "proc-macro" ) ]
7187 const _: ( ) = {
7288 use proc_macro:: * ;
73- $( trait_impl!( $trait, $type, $( ( $( $fn_attr) * ) , $fn, , $ args, $( $ret) ?, { $( $stmts) * } ) ,* ) ; ) +
89+ $( trait_impl!( $trait, $type, $( ( $( $fn_attr) * ) , $fn, $ ( { $ ( $gen ) * } ) ? , ( $ ( $ args) * ) , $( $ret) ?, { $( $stmts) * } ) ,* ) ; ) +
7490 } ;
7591 #[ cfg( feature = "proc-macro2" ) ]
7692 const _: ( ) = {
7793 use proc_macro2:: * ;
78- $( trait_impl!( $trait, $type, $( ( $( $fn_attr) * ) , $fn, , $ args, $( $ret) ?, { $( $stmts) * } ) ,* ) ; ) +
94+ $( trait_impl!( $trait, $type, $( ( $( $fn_attr) * ) , $fn, $ ( { $ ( $gen ) * } ) ? , ( $ ( $ args) * ) , $( $ret) ?, { $( $stmts) * } ) ,* ) ; ) +
7995 } ;
8096 } ;
8197 (
@@ -287,6 +303,109 @@ delimited![
287303 None as is_implicitly_delimited: " no delimiters (`Ø ... Ø`)"
288304] ;
289305
306+ impl_via_trait ! {
307+ /// Trait to parse literals
308+ impl TokenTreeLiteral for TokenTree {
309+ /// Tests if the token is a string literal.
310+ #[ must_use]
311+ fn is_string( & self ) -> bool {
312+ self . literal( ) . is_some_and( TokenTreeLiteral :: is_string)
313+ }
314+
315+ /// Returns the string contents if it is a string literal.
316+ #[ must_use]
317+ fn string( & self ) -> Option <String > {
318+ self . literal( ) . and_then( TokenTreeLiteral :: string)
319+ }
320+ }
321+
322+ impl TokenTreeLiteral for Literal {
323+ fn is_string( & self ) -> bool {
324+ let s = self . to_string( ) ;
325+ s. starts_with( '"' ) || s. starts_with( "r\" " ) || s. starts_with( "r#" )
326+ }
327+ fn string( & self ) -> Option <String > {
328+ let lit = self . to_string( ) ;
329+ if lit. starts_with( '"' ) {
330+ Some ( resolve_escapes( & lit[ 1 ..lit. len( ) - 1 ] ) )
331+ } else if lit. starts_with( 'r' ) {
332+ let pounds = lit. chars( ) . skip( 1 ) . take_while( |& c| c == '#' ) . count( ) ;
333+ Some ( lit[ 2 + pounds..lit. len( ) - pounds - 1 ] . to_owned( ) )
334+ } else {
335+ None
336+ }
337+ }
338+ }
339+ }
340+
341+ // Implemented following https://doc.rust-lang.org/reference/tokens.html#string-literals
342+ // #[allow(clippy::needless_continue)]
343+ fn resolve_escapes ( mut s : & str ) -> String {
344+ let mut out = String :: new ( ) ;
345+ while !s. is_empty ( ) {
346+ if s. starts_with ( '\\' ) {
347+ match s. as_bytes ( ) [ 1 ] {
348+ b'x' => {
349+ out. push (
350+ char:: from_u32 ( u32:: from_str_radix ( & s[ 2 ..=3 ] , 16 ) . expect ( "valid escape" ) )
351+ . expect ( "valid escape" ) ,
352+ ) ;
353+ s = & s[ 4 ..] ;
354+ }
355+ b'u' => {
356+ let len = s[ 3 ..] . find ( '}' ) . expect ( "valid escape" ) ;
357+ out. push (
358+ char:: from_u32 ( u32:: from_str_radix ( & s[ 3 ..len] , 16 ) . expect ( "valid escape" ) )
359+ . expect ( "valid escape" ) ,
360+ ) ;
361+ s = & s[ 3 + len..] ;
362+ }
363+ b'n' => {
364+ out. push ( '\n' ) ;
365+ s = & s[ 2 ..] ;
366+ }
367+ b'r' => {
368+ out. push ( '\r' ) ;
369+ s = & s[ 2 ..] ;
370+ }
371+ b't' => {
372+ out. push ( '\t' ) ;
373+ s = & s[ 2 ..] ;
374+ }
375+ b'\\' => {
376+ out. push ( '\\' ) ;
377+ s = & s[ 2 ..] ;
378+ }
379+ b'0' => {
380+ out. push ( '\0' ) ;
381+ s = & s[ 2 ..] ;
382+ }
383+ b'\'' => {
384+ out. push ( '\'' ) ;
385+ s = & s[ 2 ..] ;
386+ }
387+ b'"' => {
388+ out. push ( '"' ) ;
389+ s = & s[ 2 ..] ;
390+ }
391+ b'\n' => {
392+ s = & s[ ..s[ 2 ..]
393+ . find ( |c : char | !c. is_ascii_whitespace ( ) )
394+ . unwrap_or ( s. len ( ) ) ] ;
395+ }
396+ c => unreachable ! (
397+ "TokenStream string literals should only contain valid escapes, found `\\ {c}`"
398+ ) ,
399+ }
400+ } else {
401+ let len = s. find ( '\\' ) . unwrap_or ( s. len ( ) ) ;
402+ out. push_str ( & s[ ..len] ) ;
403+ s = & s[ len..] ;
404+ }
405+ }
406+ out
407+ }
408+
290409#[ cfg( all( test, feature = "proc-macro2" ) ) ]
291410mod test {
292411 use proc_macro2:: { Punct , Spacing , TokenTree } ;
0 commit comments