@@ -7,7 +7,8 @@ use std::str::Chars;
7
7
8
8
/// Scan string literal to tokenstream, used by most of the macros
9
9
///
10
- /// - support $var, ${var} or ${var:?} for interpolation
10
+ /// - support $var, ${var} or ${var:fmt} for interpolation, where `fmt` can be any
11
+ /// of the standard Rust formatting specifiers (e.g., `?`, `x`, `X`, `o`, `b`, `p`, `e`, `E`).
11
12
/// - to escape '$' itself, use "$$"
12
13
/// - support normal rust character escapes:
13
14
/// https://doc.rust-lang.org/reference/tokens.html#ascii-escapes
@@ -49,53 +50,58 @@ pub fn scan_str_lit(lit: &Literal) -> TokenStream {
49
50
// Before handling a variable, append any accumulated literal part.
50
51
seal_current_literal_part ( & mut output, & mut current_literal_part) ;
51
52
52
- let mut debug_format = false ; // New flag for debug formatting
53
+ let mut format_specifier = String :: new ( ) ; // To store the fmt specifier (e.g., "?", "x", "#x")
54
+ let mut is_braced_interpolation = false ;
53
55
54
56
// Check for '{' to start a braced interpolation
55
57
if chars. peek ( ) == Some ( & '{' ) {
58
+ is_braced_interpolation = true ;
56
59
chars. next ( ) ; // Consume '{'
60
+ }
57
61
58
- let var_name = parse_variable_name ( & mut chars) ;
62
+ let var_name = parse_variable_name ( & mut chars) ;
59
63
60
- // After variable name, check for ':?' for debug formatting
64
+ if is_braced_interpolation {
65
+ // If it's braced, we might have a format specifier or it might just be empty braces.
61
66
if chars. peek ( ) == Some ( & ':' ) {
62
67
chars. next ( ) ; // Consume ':'
63
- if chars. peek ( ) == Some ( & '?' ) {
64
- chars. next ( ) ; // Consume '?'
65
- debug_format = true ;
66
- } else {
67
- // If it's ':' but not ':?', then it's a malformed substitution
68
- abort ! ( lit. span( ) , "bad substitution: expected '?' after ':'" ) ;
68
+ // Read the format specifier until '}'
69
+ while let Some ( & c) = chars. peek ( ) {
70
+ if c == '}' {
71
+ break ;
72
+ }
73
+ format_specifier. push ( c) ;
74
+ chars. next ( ) ; // Consume the character of the specifier
69
75
}
70
76
}
71
77
72
78
// Expect '}' to close the braced interpolation
73
79
if chars. next ( ) != Some ( '}' ) {
74
80
abort ! ( lit. span( ) , "bad substitution: expected '}'" ) ;
75
81
}
82
+ }
76
83
77
- if !var_name. is_empty ( ) {
78
- let var_ident = syn:: parse_str :: < Ident > ( & var_name) . unwrap ( ) ;
79
- if debug_format {
80
- output. extend ( quote ! ( . append( format!( "{:?}" , #var_ident) ) ) ) ;
81
- } else {
82
- output. extend ( quote ! ( . append( format!( "{}" , #var_ident) ) ) ) ;
84
+ if !var_name. is_empty ( ) {
85
+ let var_ident = syn:: parse_str :: < Ident > ( & var_name) . unwrap ( ) ;
86
+
87
+ // To correctly handle all format specifiers (like {:02X}), we need to insert the
88
+ // entire format string *as a literal* into the format! macro.
89
+ // The `format_specifier` string itself needs to be embedded.
90
+ let format_macro_call = if format_specifier. is_empty ( ) {
91
+ quote ! {
92
+ . append( format!( "{}" , #var_ident) )
83
93
}
84
94
} else {
85
- // This covers cases like "${}" or "${:?}" with empty variable name
86
- output. extend ( quote ! ( . append( "$" ) ) ) ;
87
- }
95
+ let format_literal_str = format ! ( "{{:{}}}" , format_specifier) ;
96
+ let format_literal_token = Literal :: string ( & format_literal_str) ;
97
+ quote ! {
98
+ . append( format!( #format_literal_token, #var_ident) )
99
+ }
100
+ } ;
101
+ output. extend ( format_macro_call) ;
88
102
} else {
89
- // Handle bare $var (no braces)
90
- let var_name = parse_variable_name ( & mut chars) ;
91
- if !var_name. is_empty ( ) {
92
- let var_ident = syn:: parse_str :: < Ident > ( & var_name) . unwrap ( ) ;
93
- output. extend ( quote ! ( . append( format!( "{}" , #var_ident) ) ) ) ;
94
- } else {
95
- // If '$' is not followed by a valid variable name or a valid brace,
96
- // treat it as a literal "$".
97
- output. extend ( quote ! ( . append( "$" ) ) ) ;
98
- }
103
+ // This covers cases like "${}" or "${:?}" with empty variable name
104
+ output. extend ( quote ! ( . append( "$" ) ) ) ;
99
105
}
100
106
} else {
101
107
current_literal_part. push ( ch) ;
0 commit comments