import_attribute.rs (5984B)
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] 11 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 { 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.starts_with(&["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 { 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`) 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 }