xref: /aosp_15_r20/external/cronet/build/rust/chromium_prelude/import_attribute.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use proc_macro2::Span;
6 use quote::quote;
7 use syn::parse::{Parse, ParseStream};
8 use syn::{parse_macro_input, Error, Ident, Lit, Token};
9 
10 #[proc_macro]
import(input: proc_macro::TokenStream) -> proc_macro::TokenStream11 pub fn import(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12     let imports = parse_macro_input!(input as ImportList).imports;
13 
14     let mut stream = proc_macro2::TokenStream::new();
15     for i in imports {
16         let public = &i.reexport;
17         let mangled_crate_name = &i.target.mangled_crate_name;
18         let name = i.alias.as_ref().unwrap_or(&i.target.gn_name);
19         stream.extend(quote! {
20           #public extern crate #mangled_crate_name as #name;
21         });
22     }
23     stream.into()
24 }
25 
26 struct ImportList {
27     imports: Vec<Import>,
28 }
29 
30 struct Import {
31     target: GnTarget,
32     alias: Option<Ident>,
33     reexport: Option<Token![pub]>,
34 }
35 
36 struct GnTarget {
37     mangled_crate_name: Ident,
38     gn_name: Ident,
39 }
40 
41 impl GnTarget {
parse(s: &str, span: Span) -> Result<GnTarget, String>42     fn parse(s: &str, span: Span) -> Result<GnTarget, String> {
43         if !s.starts_with("//") {
44             return Err(String::from("expected absolute GN path (should start with //)"));
45         }
46 
47         let mut path: Vec<&str> = s[2..].split('/').collect();
48 
49         let gn_name = {
50             if path[0..2] == ["third_party", "rust"] {
51                 return Err(String::from(
52                     "import! macro should not be used for third_party crates",
53                 ));
54             }
55 
56             let last = path.pop().unwrap();
57             let (split_last, gn_name) = match last.split_once(':') {
58                 Some((last, name)) => (last, name),
59                 None => (last, last),
60             };
61             path.push(split_last);
62 
63             gn_name
64         };
65 
66         for p in &path {
67             if p.contains(':') {
68                 return Err(String::from("unexpected ':' in GN path component"));
69             }
70             if p.is_empty() {
71                 return Err(String::from("unexpected empty GN path component"));
72             }
73         }
74 
75         let mangled_crate_name =
76             escape_non_identifier_chars(&format!("{}:{gn_name}", path.join("/")))?;
77 
78         Ok(GnTarget {
79             mangled_crate_name: Ident::new(&mangled_crate_name, span),
80             gn_name: syn::parse_str::<Ident>(gn_name).map_err(|e| format!("{e}"))?,
81         })
82     }
83 }
84 
85 impl Parse for ImportList {
parse(input: ParseStream) -> syn::Result<Self>86     fn parse(input: ParseStream) -> syn::Result<Self> {
87         let mut imports: Vec<Import> = Vec::new();
88 
89         while !input.is_empty() {
90             let reexport = <Token![pub]>::parse(input).ok();
91 
92             let str_span = input.span();
93 
94             let target = match Lit::parse(input) {
95                 Err(_) => {
96                     return Err(Error::new(str_span, "expected a GN path as a string literal"));
97                 }
98                 Ok(Lit::Str(label)) => match GnTarget::parse(&label.value(), str_span) {
99                     Ok(target) => target,
100                     Err(e) => {
101                         return Err(Error::new(
102                             str_span,
103                             format!("invalid GN path {}: {}", quote::quote! {#label}, e),
104                         ));
105                     }
106                 },
107                 Ok(lit) => {
108                     return Err(Error::new(
109                         str_span,
110                         format!(
111                             "expected a GN path as string literal, found '{}' literal",
112                             quote::quote! {#lit}
113                         ),
114                     ));
115                 }
116             };
117             let alias = match <Token![as]>::parse(input) {
118                 Ok(_) => Some(Ident::parse(input)?),
119                 Err(_) => None,
120             };
121             <syn::Token![;]>::parse(input)?;
122 
123             imports.push(Import { target, alias, reexport });
124         }
125 
126         Ok(Self { imports })
127     }
128 }
129 
130 /// Escapes non-identifier characters in `symbol`.
131 ///
132 /// Importantly, this is
133 /// [an injective function](https://en.wikipedia.org/wiki/Injective_function)
134 /// which means that different inputs are never mapped to the same output.
135 ///
136 /// This is based on a similar function in
137 /// https://github.com/google/crubit/blob/22ab04aef9f7cc56d8600c310c7fe20999ffc41b/common/code_gen_utils.rs#L59-L71
138 /// The main differences are:
139 ///
140 /// * Only a limited set of special characters is supported, because this makes
141 ///   it easier to replicate the escaping algorithm in `.gni` files, using just
142 ///   `string_replace` calls.
143 /// * No dependency on `unicode_ident` crate means that instead of
144 ///   `is_xid_continue` a more restricted call to `char::is_ascii_alphanumeric`
145 ///   is used.
146 /// * No support for escaping leading digits.
147 /// * The escapes are slightly different (e.g. `/` frequently appears in GN
148 ///   paths and therefore here we map it to a nice `_s` rather than to `_x002f`)
escape_non_identifier_chars(symbol: &str) -> Result<String, String>149 fn escape_non_identifier_chars(symbol: &str) -> Result<String, String> {
150     assert!(!symbol.is_empty()); // Caller is expected to verify.
151     if symbol.chars().next().unwrap().is_ascii_digit() {
152         return Err("Leading digits are not supported".to_string());
153     }
154 
155     // Escaping every character can at most double the size of the string.
156     let mut result = String::with_capacity(symbol.len() * 2);
157     for c in symbol.chars() {
158         // NOTE: TargetName=>CrateName mangling algorithm should be updated
159         // simultaneously in 3 places: here, //build/rust/rust_target.gni,
160         // //build/rust/rust_static_library.gni.
161         match c {
162             '_' => result.push_str("_u"),
163             '/' => result.push_str("_s"),
164             ':' => result.push_str("_c"),
165             '-' => result.push_str("_d"),
166             c if c.is_ascii_alphanumeric() => result.push(c),
167             _ => return Err(format!("Unsupported character in GN path component: `{c}`")),
168         }
169     }
170 
171     Ok(result)
172 }
173