1#![cfg_attr(
7 not(test),
8 deny(
9 clippy::indexing_slicing,
10 clippy::unwrap_used,
11 clippy::expect_used,
12 clippy::exhaustive_structs,
15 clippy::exhaustive_enums,
16 missing_debug_implementations,
17 )
18)]
19#![warn(missing_docs)]
20
21extern crate proc_macro;
26use proc_macro::TokenStream;
27use proc_macro2::Span;
28use proc_macro2::TokenStream as TokenStream2;
29use quote::quote;
30use syn::parenthesized;
31use syn::parse::{self, Parse, ParseStream};
32use syn::parse_macro_input;
33use syn::punctuated::Punctuated;
34use syn::spanned::Spanned;
35use syn::DeriveInput;
36use syn::{Ident, LitStr, Path, Token};
37#[cfg(test)]
38mod tests;
39
40#[proc_macro_attribute]
41
42pub fn data_struct(attr: TokenStream, item: TokenStream) -> TokenStream {
107 TokenStream::from(data_struct_impl(
108 parse_macro_input!(attr as DataStructArgs),
109 parse_macro_input!(item as DeriveInput),
110 ))
111}
112
113pub(crate) struct DataStructArgs {
114 args: Punctuated<DataStructArg, Token![,]>,
115}
116
117impl Parse for DataStructArgs {
118 fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
119 let args = input.parse_terminated(DataStructArg::parse, Token![,])?;
120 Ok(Self { args })
121 }
122}
123struct DataStructArg {
124 marker_name: Path,
125 key_lit: Option<LitStr>,
126 fallback_by: Option<LitStr>,
127 extension_key: Option<LitStr>,
128 fallback_supplement: Option<LitStr>,
129 singleton: bool,
130}
131
132impl DataStructArg {
133 fn new(marker_name: Path) -> Self {
134 Self {
135 marker_name,
136 key_lit: None,
137 fallback_by: None,
138 extension_key: None,
139 fallback_supplement: None,
140 singleton: false,
141 }
142 }
143}
144
145impl Parse for DataStructArg {
146 fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
147 let path: Path = input.parse()?;
148
149 fn at_most_one_option<T>(
150 o: &mut Option<T>,
151 new: T,
152 name: &str,
153 span: Span,
154 ) -> parse::Result<()> {
155 if o.replace(new).is_some() {
156 Err(parse::Error::new(
157 span,
158 format!("marker() cannot contain multiple {name}s"),
159 ))
160 } else {
161 Ok(())
162 }
163 }
164
165 if path.is_ident("marker") {
166 let content;
167 let paren = parenthesized!(content in input);
168 let mut marker_name: Option<Path> = None;
169 let mut key_lit: Option<LitStr> = None;
170 let mut fallback_by: Option<LitStr> = None;
171 let mut extension_key: Option<LitStr> = None;
172 let mut fallback_supplement: Option<LitStr> = None;
173 let mut singleton = false;
174 let punct = content.parse_terminated(DataStructMarkerArg::parse, Token![,])?;
175
176 for entry in punct {
177 match entry {
178 DataStructMarkerArg::Path(path) => {
179 at_most_one_option(&mut marker_name, path, "marker", input.span())?;
180 }
181 DataStructMarkerArg::NameValue(name, value) => {
182 if name == "fallback_by" {
183 at_most_one_option(
184 &mut fallback_by,
185 value,
186 "fallback_by",
187 paren.span.join(),
188 )?;
189 } else if name == "extension_key" {
190 at_most_one_option(
191 &mut extension_key,
192 value,
193 "extension_key",
194 paren.span.join(),
195 )?;
196 } else if name == "fallback_supplement" {
197 at_most_one_option(
198 &mut fallback_supplement,
199 value,
200 "fallback_supplement",
201 paren.span.join(),
202 )?;
203 } else {
204 return Err(parse::Error::new(
205 name.span(),
206 format!("unknown option {name} in marker()"),
207 ));
208 }
209 }
210 DataStructMarkerArg::Lit(lit) => {
211 at_most_one_option(&mut key_lit, lit, "literal key", input.span())?;
212 }
213 DataStructMarkerArg::Singleton => {
214 singleton = true;
215 }
216 }
217 }
218 let marker_name = if let Some(marker_name) = marker_name {
219 marker_name
220 } else {
221 return Err(parse::Error::new(
222 input.span(),
223 "marker() must contain a marker!",
224 ));
225 };
226
227 Ok(Self {
228 marker_name,
229 key_lit,
230 fallback_by,
231 extension_key,
232 fallback_supplement,
233 singleton,
234 })
235 } else {
236 let mut this = DataStructArg::new(path);
237 let lookahead = input.lookahead1();
238 if lookahead.peek(Token![=]) {
239 let _t: Token![=] = input.parse()?;
240 let lit: LitStr = input.parse()?;
241 this.key_lit = Some(lit);
242 Ok(this)
243 } else {
244 Ok(this)
245 }
246 }
247 }
248}
249
250enum DataStructMarkerArg {
252 Path(Path),
253 NameValue(Ident, LitStr),
254 Lit(LitStr),
255 Singleton,
256}
257impl Parse for DataStructMarkerArg {
258 fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
259 let lookahead = input.lookahead1();
260 if lookahead.peek(LitStr) {
261 Ok(DataStructMarkerArg::Lit(input.parse()?))
262 } else {
263 let path: Path = input.parse()?;
264 let lookahead = input.lookahead1();
265 if lookahead.peek(Token![=]) {
266 let _tok: Token![=] = input.parse()?;
267 let ident = path.get_ident().ok_or_else(|| {
268 parse::Error::new(path.span(), "Expected identifier before `=`, found path")
269 })?;
270 Ok(DataStructMarkerArg::NameValue(
271 ident.clone(),
272 input.parse()?,
273 ))
274 } else if path.is_ident("singleton") {
275 Ok(DataStructMarkerArg::Singleton)
276 } else {
277 Ok(DataStructMarkerArg::Path(path))
278 }
279 }
280 }
281}
282
283fn data_struct_impl(attr: DataStructArgs, input: DeriveInput) -> TokenStream2 {
284 if input.generics.type_params().count() > 0 {
285 return syn::Error::new(
286 input.generics.span(),
287 "#[data_struct] does not support type parameters",
288 )
289 .to_compile_error();
290 }
291 let lifetimes = input.generics.lifetimes().collect::<Vec<_>>();
292
293 let name = &input.ident;
294
295 let name_with_lt = if !lifetimes.is_empty() {
296 quote!(#name<'static>)
297 } else {
298 quote!(#name)
299 };
300
301 if lifetimes.len() > 1 {
302 return syn::Error::new(
303 input.generics.span(),
304 "#[data_struct] does not support more than one lifetime parameter",
305 )
306 .to_compile_error();
307 }
308
309 let bake_derive = input
310 .attrs
311 .iter()
312 .find(|a| a.path().is_ident("databake"))
313 .map(|a| {
314 quote! {
315 #[derive(databake::Bake)]
316 #a
317 }
318 })
319 .unwrap_or_else(|| quote! {});
320
321 let mut result = TokenStream2::new();
322
323 for single_attr in attr.args {
324 let DataStructArg {
325 marker_name,
326 key_lit,
327 fallback_by,
328 extension_key,
329 fallback_supplement,
330 singleton,
331 } = single_attr;
332
333 let docs = if let Some(ref key_lit) = key_lit {
334 let fallback_by_docs_str = match fallback_by {
335 Some(ref fallback_by) => fallback_by.value(),
336 None => "language (default)".to_string(),
337 };
338 let extension_key_docs_str = match extension_key {
339 Some(ref extension_key) => extension_key.value(),
340 None => "none (default)".to_string(),
341 };
342 format!("Marker type for [`{}`]: \"{}\"\n\n- Fallback priority: {}\n- Extension keyword: {}", name, key_lit.value(), fallback_by_docs_str, extension_key_docs_str)
343 } else {
344 format!("Marker type for [`{name}`]")
345 };
346
347 result.extend(quote!(
348 #[doc = #docs]
349 #bake_derive
350 pub struct #marker_name;
351 impl icu_provider::DataMarker for #marker_name {
352 type Yokeable = #name_with_lt;
353 }
354 ));
355
356 if let Some(key_lit) = key_lit {
357 let key_str = key_lit.value();
358 let fallback_by_expr = if let Some(fallback_by_lit) = fallback_by {
359 match fallback_by_lit.value().as_str() {
360 "region" => {
361 quote! {icu_provider::_internal::LocaleFallbackPriority::Region}
362 }
363 "collation" => {
364 quote! {icu_provider::_internal::LocaleFallbackPriority::Collation}
365 }
366 "language" => {
367 quote! {icu_provider::_internal::LocaleFallbackPriority::Language}
368 }
369 _ => panic!("Invalid value for fallback_by"),
370 }
371 } else {
372 quote! {icu_provider::_internal::LocaleFallbackPriority::const_default()}
373 };
374 let extension_key_expr = if let Some(extension_key_lit) = extension_key {
375 quote! {Some(icu_provider::_internal::locid::extensions::unicode::key!(#extension_key_lit))}
376 } else {
377 quote! {None}
378 };
379 let fallback_supplement_expr = if let Some(fallback_supplement_lit) =
380 fallback_supplement
381 {
382 match fallback_supplement_lit.value().as_str() {
383 "collation" => {
384 quote! {Some(icu_provider::_internal::LocaleFallbackSupplement::Collation)}
385 }
386 _ => panic!("Invalid value for fallback_supplement"),
387 }
388 } else {
389 quote! {None}
390 };
391 result.extend(quote!(
392 impl icu_provider::KeyedDataMarker for #marker_name {
393 const KEY: icu_provider::DataKey = icu_provider::data_key!(#key_str, icu_provider::DataKeyMetadata::construct_internal(
394 #fallback_by_expr,
395 #extension_key_expr,
396 #fallback_supplement_expr,
397 #singleton,
398 ));
399 }
400 ));
401 }
402 }
403
404 result.extend(quote!(
405 #[derive(icu_provider::prelude::yoke::Yokeable, icu_provider::prelude::zerofrom::ZeroFrom)]
406 #input
407 ));
408
409 result
410}