@@ -26,7 +26,7 @@ use proc_macro2::{Span, TokenStream};
26
26
use quote:: quote;
27
27
use std:: collections:: HashMap ;
28
28
use syn:: parse:: { Parse , ParseStream , Result } ;
29
- use syn:: { braced, punctuated:: Punctuated , Ident , LitStr , Token } ;
29
+ use syn:: { braced, punctuated:: Punctuated , Expr , Ident , Lit , LitStr , Macro , Token } ;
30
30
31
31
#[ cfg( test) ]
32
32
mod tests;
@@ -53,21 +53,46 @@ impl Parse for Keyword {
53
53
54
54
struct Symbol {
55
55
name : Ident ,
56
- value : Option < LitStr > ,
56
+ value : Value ,
57
+ }
58
+
59
+ enum Value {
60
+ SameAsName ,
61
+ String ( LitStr ) ,
62
+ Env ( LitStr , Macro ) ,
63
+ Unsupported ( Expr ) ,
57
64
}
58
65
59
66
impl Parse for Symbol {
60
67
fn parse ( input : ParseStream < ' _ > ) -> Result < Self > {
61
68
let name = input. parse ( ) ?;
62
- let value = match input. parse :: < Token ! [ : ] > ( ) {
63
- Ok ( _) => Some ( input. parse ( ) ?) ,
64
- Err ( _) => None ,
65
- } ;
69
+ let colon_token: Option < Token ! [ : ] > = input. parse ( ) ?;
70
+ let value = if colon_token. is_some ( ) { input. parse ( ) ? } else { Value :: SameAsName } ;
66
71
67
72
Ok ( Symbol { name, value } )
68
73
}
69
74
}
70
75
76
+ impl Parse for Value {
77
+ fn parse ( input : ParseStream < ' _ > ) -> Result < Self > {
78
+ let expr: Expr = input. parse ( ) ?;
79
+ match & expr {
80
+ Expr :: Lit ( expr) => {
81
+ if let Lit :: Str ( lit) = & expr. lit {
82
+ return Ok ( Value :: String ( lit. clone ( ) ) ) ;
83
+ }
84
+ }
85
+ Expr :: Macro ( expr) => {
86
+ if expr. mac . path . is_ident ( "env" ) && let Ok ( lit) = expr. mac . parse_body ( ) {
87
+ return Ok ( Value :: Env ( lit, expr. mac . clone ( ) ) ) ;
88
+ }
89
+ }
90
+ _ => { }
91
+ }
92
+ Ok ( Value :: Unsupported ( expr) )
93
+ }
94
+ }
95
+
71
96
struct Input {
72
97
keywords : Punctuated < Keyword , Token ! [ , ] > ,
73
98
symbols : Punctuated < Symbol , Token ! [ , ] > ,
@@ -111,6 +136,37 @@ pub fn symbols(input: TokenStream) -> TokenStream {
111
136
output
112
137
}
113
138
139
+ struct Preinterned {
140
+ idx : u32 ,
141
+ span_of_name : Span ,
142
+ }
143
+
144
+ struct Entries {
145
+ map : HashMap < String , Preinterned > ,
146
+ }
147
+
148
+ impl Entries {
149
+ fn with_capacity ( capacity : usize ) -> Self {
150
+ Entries { map : HashMap :: with_capacity ( capacity) }
151
+ }
152
+
153
+ fn insert ( & mut self , span : Span , str : & str , errors : & mut Errors ) -> u32 {
154
+ if let Some ( prev) = self . map . get ( str) {
155
+ errors. error ( span, format ! ( "Symbol `{str}` is duplicated" ) ) ;
156
+ errors. error ( prev. span_of_name , "location of previous definition" . to_string ( ) ) ;
157
+ prev. idx
158
+ } else {
159
+ let idx = self . len ( ) ;
160
+ self . map . insert ( str. to_string ( ) , Preinterned { idx, span_of_name : span } ) ;
161
+ idx
162
+ }
163
+ }
164
+
165
+ fn len ( & self ) -> u32 {
166
+ u32:: try_from ( self . map . len ( ) ) . expect ( "way too many symbols" )
167
+ }
168
+ }
169
+
114
170
fn symbols_with_errors ( input : TokenStream ) -> ( TokenStream , Vec < syn:: Error > ) {
115
171
let mut errors = Errors :: default ( ) ;
116
172
@@ -127,20 +183,9 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
127
183
let mut keyword_stream = quote ! { } ;
128
184
let mut symbols_stream = quote ! { } ;
129
185
let mut prefill_stream = quote ! { } ;
130
- let mut counter = 0u32 ;
131
- let mut keys =
132
- HashMap :: < String , Span > :: with_capacity ( input. keywords . len ( ) + input. symbols . len ( ) + 10 ) ;
186
+ let mut entries = Entries :: with_capacity ( input. keywords . len ( ) + input. symbols . len ( ) + 10 ) ;
133
187
let mut prev_key: Option < ( Span , String ) > = None ;
134
188
135
- let mut check_dup = |span : Span , str : & str , errors : & mut Errors | {
136
- if let Some ( prev_span) = keys. get ( str) {
137
- errors. error ( span, format ! ( "Symbol `{str}` is duplicated" ) ) ;
138
- errors. error ( * prev_span, "location of previous definition" . to_string ( ) ) ;
139
- } else {
140
- keys. insert ( str. to_string ( ) , span) ;
141
- }
142
- } ;
143
-
144
189
let mut check_order = |span : Span , str : & str , errors : & mut Errors | {
145
190
if let Some ( ( prev_span, ref prev_str) ) = prev_key {
146
191
if str < prev_str {
@@ -156,49 +201,98 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
156
201
let name = & keyword. name ;
157
202
let value = & keyword. value ;
158
203
let value_string = value. value ( ) ;
159
- check_dup ( keyword. name . span ( ) , & value_string, & mut errors) ;
204
+ let idx = entries . insert ( keyword. name . span ( ) , & value_string, & mut errors) ;
160
205
prefill_stream. extend ( quote ! {
161
206
#value,
162
207
} ) ;
163
208
keyword_stream. extend ( quote ! {
164
- pub const #name: Symbol = Symbol :: new( #counter ) ;
209
+ pub const #name: Symbol = Symbol :: new( #idx ) ;
165
210
} ) ;
166
- counter += 1 ;
167
211
}
168
212
169
213
// Generate the listed symbols.
170
214
for symbol in input. symbols . iter ( ) {
171
215
let name = & symbol. name ;
216
+ check_order ( symbol. name . span ( ) , & name. to_string ( ) , & mut errors) ;
217
+
172
218
let value = match & symbol. value {
173
- Some ( value) => value. value ( ) ,
174
- None => name. to_string ( ) ,
219
+ Value :: SameAsName => name. to_string ( ) ,
220
+ Value :: String ( lit) => lit. value ( ) ,
221
+ Value :: Env ( ..) => continue , // in another loop below
222
+ Value :: Unsupported ( expr) => {
223
+ errors. list . push ( syn:: Error :: new_spanned (
224
+ expr,
225
+ concat ! (
226
+ "unsupported expression for symbol value; implement support for this in " ,
227
+ file!( ) ,
228
+ ) ,
229
+ ) ) ;
230
+ continue ;
231
+ }
175
232
} ;
176
- check_dup ( symbol. name . span ( ) , & value, & mut errors) ;
177
- check_order ( symbol. name . span ( ) , & name. to_string ( ) , & mut errors) ;
233
+ let idx = entries. insert ( symbol. name . span ( ) , & value, & mut errors) ;
178
234
179
235
prefill_stream. extend ( quote ! {
180
236
#value,
181
237
} ) ;
182
238
symbols_stream. extend ( quote ! {
183
- pub const #name: Symbol = Symbol :: new( #counter ) ;
239
+ pub const #name: Symbol = Symbol :: new( #idx ) ;
184
240
} ) ;
185
- counter += 1 ;
186
241
}
187
242
188
243
// Generate symbols for the strings "0", "1", ..., "9".
189
- let digits_base = counter;
190
- counter += 10 ;
191
244
for n in 0 ..10 {
192
245
let n = n. to_string ( ) ;
193
- check_dup ( Span :: call_site ( ) , & n, & mut errors) ;
246
+ entries . insert ( Span :: call_site ( ) , & n, & mut errors) ;
194
247
prefill_stream. extend ( quote ! {
195
248
#n,
196
249
} ) ;
197
250
}
198
251
252
+ // Symbols whose value comes from an environment variable. It's allowed for
253
+ // these to have the same value as another symbol.
254
+ for symbol in & input. symbols {
255
+ let ( env_var, expr) = match & symbol. value {
256
+ Value :: Env ( lit, expr) => ( lit, expr) ,
257
+ Value :: SameAsName | Value :: String ( _) | Value :: Unsupported ( _) => continue ,
258
+ } ;
259
+
260
+ if !proc_macro:: is_available ( ) {
261
+ errors. error (
262
+ Span :: call_site ( ) ,
263
+ "proc_macro::tracked_env is not available in unit test" . to_owned ( ) ,
264
+ ) ;
265
+ break ;
266
+ }
267
+
268
+ let value = match proc_macro:: tracked_env:: var ( env_var. value ( ) ) {
269
+ Ok ( value) => value,
270
+ Err ( err) => {
271
+ errors. list . push ( syn:: Error :: new_spanned ( expr, err) ) ;
272
+ continue ;
273
+ }
274
+ } ;
275
+
276
+ let idx = if let Some ( prev) = entries. map . get ( & value) {
277
+ prev. idx
278
+ } else {
279
+ prefill_stream. extend ( quote ! {
280
+ #value,
281
+ } ) ;
282
+ entries. insert ( symbol. name . span ( ) , & value, & mut errors)
283
+ } ;
284
+
285
+ let name = & symbol. name ;
286
+ symbols_stream. extend ( quote ! {
287
+ pub const #name: Symbol = Symbol :: new( #idx) ;
288
+ } ) ;
289
+ }
290
+
291
+ let symbol_digits_base = entries. map [ "0" ] . idx ;
292
+ let preinterned_symbols_count = entries. len ( ) ;
199
293
let output = quote ! {
200
- const SYMBOL_DIGITS_BASE : u32 = #digits_base ;
201
- const PREINTERNED_SYMBOLS_COUNT : u32 = #counter ;
294
+ const SYMBOL_DIGITS_BASE : u32 = #symbol_digits_base ;
295
+ const PREINTERNED_SYMBOLS_COUNT : u32 = #preinterned_symbols_count ;
202
296
203
297
#[ doc( hidden) ]
204
298
#[ allow( non_upper_case_globals) ]
0 commit comments