1
- use proc_macro:: TokenStream ;
1
+ //! Proc macro which builds the Symbol table
2
+ //!
3
+ //! # Debugging
4
+ //!
5
+ //! Since this proc-macro does some non-trivial work, debugging it is important.
6
+ //! This proc-macro can be invoked as an ordinary unit test, like so:
7
+ //!
8
+ //! ```bash
9
+ //! cd compiler/rustc_macros
10
+ //! cargo test symbols::test_symbols -- --nocapture
11
+ //! ```
12
+ //!
13
+ //! This unit test finds the `symbols!` invocation in `compiler/rustc_span/src/symbol.rs`
14
+ //! and runs it. It verifies that the output token stream can be parsed as valid module
15
+ //! items and that no errors were produced.
16
+ //!
17
+ //! You can also view the generated code by using `cargo expand`:
18
+ //!
19
+ //! ```bash
20
+ //! cargo install cargo-expand # this is necessary only once
21
+ //! cd compiler/rustc_span
22
+ //! cargo expand > /tmp/rustc_span.rs # it's a big file
23
+ //! ```
24
+
25
+ use proc_macro2:: { Span , TokenStream } ;
2
26
use quote:: quote;
3
- use std:: collections:: HashSet ;
27
+ use std:: collections:: HashMap ;
4
28
use syn:: parse:: { Parse , ParseStream , Result } ;
5
- use syn:: { braced, parse_macro_input, Ident , LitStr , Token } ;
29
+ use syn:: { braced, punctuated:: Punctuated , Ident , LitStr , Token } ;
30
+
31
+ #[ cfg( test) ]
32
+ mod tests;
6
33
7
34
mod kw {
8
35
syn:: custom_keyword!( Keywords ) ;
@@ -19,7 +46,6 @@ impl Parse for Keyword {
19
46
let name = input. parse ( ) ?;
20
47
input. parse :: < Token ! [ : ] > ( ) ?;
21
48
let value = input. parse ( ) ?;
22
- input. parse :: < Token ! [ , ] > ( ) ?;
23
49
24
50
Ok ( Keyword { name, value } )
25
51
}
@@ -37,78 +63,101 @@ impl Parse for Symbol {
37
63
Ok ( _) => Some ( input. parse ( ) ?) ,
38
64
Err ( _) => None ,
39
65
} ;
40
- input. parse :: < Token ! [ , ] > ( ) ?;
41
66
42
67
Ok ( Symbol { name, value } )
43
68
}
44
69
}
45
70
46
- /// A type used to greedily parse another type until the input is empty.
47
- struct List < T > ( Vec < T > ) ;
48
-
49
- impl < T : Parse > Parse for List < T > {
50
- fn parse ( input : ParseStream < ' _ > ) -> Result < Self > {
51
- let mut list = Vec :: new ( ) ;
52
- while !input. is_empty ( ) {
53
- list. push ( input. parse ( ) ?) ;
54
- }
55
- Ok ( List ( list) )
56
- }
57
- }
58
-
59
71
struct Input {
60
- keywords : List < Keyword > ,
61
- symbols : List < Symbol > ,
72
+ keywords : Punctuated < Keyword , Token ! [ , ] > ,
73
+ symbols : Punctuated < Symbol , Token ! [ , ] > ,
62
74
}
63
75
64
76
impl Parse for Input {
65
77
fn parse ( input : ParseStream < ' _ > ) -> Result < Self > {
66
78
input. parse :: < kw:: Keywords > ( ) ?;
67
79
let content;
68
80
braced ! ( content in input) ;
69
- let keywords = content . parse ( ) ?;
81
+ let keywords = Punctuated :: parse_terminated ( & content ) ?;
70
82
71
83
input. parse :: < kw:: Symbols > ( ) ?;
72
84
let content;
73
85
braced ! ( content in input) ;
74
- let symbols = content . parse ( ) ?;
86
+ let symbols = Punctuated :: parse_terminated ( & content ) ?;
75
87
76
88
Ok ( Input { keywords, symbols } )
77
89
}
78
90
}
79
91
92
+ #[ derive( Default ) ]
93
+ struct Errors {
94
+ list : Vec < syn:: Error > ,
95
+ }
96
+
97
+ impl Errors {
98
+ fn error ( & mut self , span : Span , message : String ) {
99
+ self . list . push ( syn:: Error :: new ( span, message) ) ;
100
+ }
101
+ }
102
+
80
103
pub fn symbols ( input : TokenStream ) -> TokenStream {
81
- let input = parse_macro_input ! ( input as Input ) ;
104
+ let ( mut output, errors) = symbols_with_errors ( input) ;
105
+
106
+ // If we generated any errors, then report them as compiler_error!() macro calls.
107
+ // This lets the errors point back to the most relevant span. It also allows us
108
+ // to report as many errors as we can during a single run.
109
+ output. extend ( errors. into_iter ( ) . map ( |e| e. to_compile_error ( ) ) ) ;
110
+
111
+ output
112
+ }
113
+
114
+ fn symbols_with_errors ( input : TokenStream ) -> ( TokenStream , Vec < syn:: Error > ) {
115
+ let mut errors = Errors :: default ( ) ;
116
+
117
+ let input: Input = match syn:: parse2 ( input) {
118
+ Ok ( input) => input,
119
+ Err ( e) => {
120
+ // This allows us to display errors at the proper span, while minimizing
121
+ // unrelated errors caused by bailing out (and not generating code).
122
+ errors. list . push ( e) ;
123
+ Input { keywords : Default :: default ( ) , symbols : Default :: default ( ) }
124
+ }
125
+ } ;
82
126
83
127
let mut keyword_stream = quote ! { } ;
84
128
let mut symbols_stream = quote ! { } ;
85
129
let mut digits_stream = quote ! { } ;
86
130
let mut prefill_stream = quote ! { } ;
87
131
let mut counter = 0u32 ;
88
- let mut keys = HashSet :: < String > :: new ( ) ;
89
- let mut prev_key: Option < String > = None ;
90
- let mut errors = Vec :: < String > :: new ( ) ;
91
-
92
- let mut check_dup = |str : & str , errors : & mut Vec < String > | {
93
- if !keys. insert ( str. to_string ( ) ) {
94
- errors. push ( format ! ( "Symbol `{}` is duplicated" , str ) ) ;
132
+ let mut keys =
133
+ HashMap :: < String , Span > :: with_capacity ( input. keywords . len ( ) + input. symbols . len ( ) + 10 ) ;
134
+ let mut prev_key: Option < ( Span , String ) > = None ;
135
+
136
+ let mut check_dup = |span : Span , str : & str , errors : & mut Errors | {
137
+ if let Some ( prev_span) = keys. get ( str) {
138
+ errors. error ( span, format ! ( "Symbol `{}` is duplicated" , str ) ) ;
139
+ errors. error ( * prev_span, format ! ( "location of previous definition" ) ) ;
140
+ } else {
141
+ keys. insert ( str. to_string ( ) , span) ;
95
142
}
96
143
} ;
97
144
98
- let mut check_order = |str : & str , errors : & mut Vec < String > | {
99
- if let Some ( ref prev_str) = prev_key {
145
+ let mut check_order = |span : Span , str : & str , errors : & mut Errors | {
146
+ if let Some ( ( prev_span , ref prev_str) ) = prev_key {
100
147
if str < prev_str {
101
- errors. push ( format ! ( "Symbol `{}` must precede `{}`" , str , prev_str) ) ;
148
+ errors. error ( span, format ! ( "Symbol `{}` must precede `{}`" , str , prev_str) ) ;
149
+ errors. error ( prev_span, format ! ( "location of previous symbol `{}`" , prev_str) ) ;
102
150
}
103
151
}
104
- prev_key = Some ( str. to_string ( ) ) ;
152
+ prev_key = Some ( ( span , str. to_string ( ) ) ) ;
105
153
} ;
106
154
107
155
// Generate the listed keywords.
108
- for keyword in & input. keywords . 0 {
156
+ for keyword in input. keywords . iter ( ) {
109
157
let name = & keyword. name ;
110
158
let value = & keyword. value ;
111
- check_dup ( & value. value ( ) , & mut errors) ;
159
+ let value_string = value. value ( ) ;
160
+ check_dup ( keyword. name . span ( ) , & value_string, & mut errors) ;
112
161
prefill_stream. extend ( quote ! {
113
162
#value,
114
163
} ) ;
@@ -120,14 +169,15 @@ pub fn symbols(input: TokenStream) -> TokenStream {
120
169
}
121
170
122
171
// Generate the listed symbols.
123
- for symbol in & input. symbols . 0 {
172
+ for symbol in input. symbols . iter ( ) {
124
173
let name = & symbol. name ;
125
174
let value = match & symbol. value {
126
175
Some ( value) => value. value ( ) ,
127
176
None => name. to_string ( ) ,
128
177
} ;
129
- check_dup ( & value, & mut errors) ;
130
- check_order ( & name. to_string ( ) , & mut errors) ;
178
+ check_dup ( symbol. name . span ( ) , & value, & mut errors) ;
179
+ check_order ( symbol. name . span ( ) , & name. to_string ( ) , & mut errors) ;
180
+
131
181
prefill_stream. extend ( quote ! {
132
182
#value,
133
183
} ) ;
@@ -142,7 +192,7 @@ pub fn symbols(input: TokenStream) -> TokenStream {
142
192
// Generate symbols for the strings "0", "1", ..., "9".
143
193
for n in 0 ..10 {
144
194
let n = n. to_string ( ) ;
145
- check_dup ( & n, & mut errors) ;
195
+ check_dup ( Span :: call_site ( ) , & n, & mut errors) ;
146
196
prefill_stream. extend ( quote ! {
147
197
#n,
148
198
} ) ;
@@ -152,14 +202,7 @@ pub fn symbols(input: TokenStream) -> TokenStream {
152
202
counter += 1 ;
153
203
}
154
204
155
- if !errors. is_empty ( ) {
156
- for error in errors. into_iter ( ) {
157
- eprintln ! ( "error: {}" , error)
158
- }
159
- panic ! ( "errors in `Keywords` and/or `Symbols`" ) ;
160
- }
161
-
162
- let tt = TokenStream :: from ( quote ! {
205
+ let output = quote ! {
163
206
macro_rules! keywords {
164
207
( ) => {
165
208
#keyword_stream
@@ -184,11 +227,16 @@ pub fn symbols(input: TokenStream) -> TokenStream {
184
227
] )
185
228
}
186
229
}
187
- } ) ;
230
+ } ;
188
231
189
- // To see the generated code generated, uncomment this line, recompile, and
190
- // run the resulting output through `rustfmt`.
191
- //eprintln!("{}", tt);
232
+ ( output, errors. list )
192
233
193
- tt
234
+ // To see the generated code, use the "cargo expand" command.
235
+ // Do this once to install:
236
+ // cargo install cargo-expand
237
+ //
238
+ // Then, cd to rustc_span and run:
239
+ // cargo expand > /tmp/rustc_span_expanded.rs
240
+ //
241
+ // and read that file.
194
242
}
0 commit comments