blob: 85130ab0e2720692b0aa04982c32a099e9278745 [file] [log] [blame]
Marcel Hlopko42abfc82021-08-09 07:03:17 +00001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
Michael Forstercc5941a2021-10-07 07:12:24 +00005use anyhow::{anyhow, bail, Result};
Marcel Hlopko884ae7f2021-08-18 13:58:22 +00006use ffi_types::*;
Marcel Hlopko42abfc82021-08-09 07:03:17 +00007use ir::*;
8use itertools::Itertools;
Googler5ea88642021-09-29 08:05:59 +00009use proc_macro2::{Ident, Literal, TokenStream};
Marcel Hlopko42abfc82021-08-09 07:03:17 +000010use quote::format_ident;
11use quote::quote;
Devin Jeanpierre273eeae2021-10-06 13:29:35 +000012use std::collections::BTreeSet;
Michael Forstere9c881c2021-10-01 09:38:19 +000013use std::io::Write;
Marcel Hlopko42abfc82021-08-09 07:03:17 +000014use std::iter::Iterator;
15use std::panic::catch_unwind;
16use std::process;
Michael Forstere9c881c2021-10-01 09:38:19 +000017use std::process::{Command, Stdio};
Marcel Hlopko42abfc82021-08-09 07:03:17 +000018
Marcel Hlopko45fba972021-08-23 19:52:20 +000019/// FFI equivalent of `Bindings`.
20#[repr(C)]
21pub struct FfiBindings {
22 rs_api: FfiU8SliceBox,
23 rs_api_impl: FfiU8SliceBox,
24}
25
26/// Deserializes IR from `json` and generates bindings source code.
Marcel Hlopko42abfc82021-08-09 07:03:17 +000027///
28/// This function panics on error.
29///
30/// Ownership:
Michael Forsterbee84482021-10-13 08:35:38 +000031/// * function doesn't take ownership of (in other words it borrows) the
32/// param `json`
Marcel Hlopko42abfc82021-08-09 07:03:17 +000033/// * function passes ownership of the returned value to the caller
34///
35/// Safety:
Michael Forsterbee84482021-10-13 08:35:38 +000036/// * function expects that param `json` is a FfiU8Slice for a valid array of
37/// bytes with the given size.
Marcel Hlopko42abfc82021-08-09 07:03:17 +000038/// * function expects that param `json` doesn't change during the call.
39#[no_mangle]
Marcel Hlopko45fba972021-08-23 19:52:20 +000040pub unsafe extern "C" fn GenerateBindingsImpl(json: FfiU8Slice) -> FfiBindings {
Marcel Hlopko42abfc82021-08-09 07:03:17 +000041 catch_unwind(|| {
Marcel Hlopko45fba972021-08-23 19:52:20 +000042 // It is ok to abort here.
43 let Bindings { rs_api, rs_api_impl } = generate_bindings(json.as_slice()).unwrap();
44
45 FfiBindings {
46 rs_api: FfiU8SliceBox::from_boxed_slice(rs_api.into_bytes().into_boxed_slice()),
47 rs_api_impl: FfiU8SliceBox::from_boxed_slice(
48 rs_api_impl.into_bytes().into_boxed_slice(),
49 ),
50 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +000051 })
52 .unwrap_or_else(|_| process::abort())
53}
54
Marcel Hlopko45fba972021-08-23 19:52:20 +000055/// Source code for generated bindings.
56struct Bindings {
57 // Rust source code.
58 rs_api: String,
59 // C++ source code.
60 rs_api_impl: String,
Marcel Hlopko42abfc82021-08-09 07:03:17 +000061}
62
Marcel Hlopko45fba972021-08-23 19:52:20 +000063fn generate_bindings(json: &[u8]) -> Result<Bindings> {
64 let ir = deserialize_ir(json)?;
65 let rs_api = generate_rs_api(&ir)?;
66 let rs_api_impl = generate_rs_api_impl(&ir)?;
67 Ok(Bindings { rs_api, rs_api_impl })
68}
69
Devin Jeanpierre273eeae2021-10-06 13:29:35 +000070/// Source code with attached information about how to modify the parent crate.
71///
Michael Forsterbee84482021-10-13 08:35:38 +000072/// For example, the snippet `vec![].into_raw_parts()` is not valid unless the
73/// `vec_into_raw_parts` feature is enabled. So such a snippet should be
74/// represented as:
Devin Jeanpierre273eeae2021-10-06 13:29:35 +000075///
76/// ```
77/// RsSnippet {
78/// features: btree_set!["vec_into_raw_parts"],
79/// tokens: quote!{vec![].into_raw_parts()},
80/// }
81/// ```
82struct RsSnippet {
83 /// Rust feature flags used by this snippet.
84 features: BTreeSet<Ident>,
85 /// The snippet itself, as a token stream.
86 tokens: TokenStream,
87}
88
89impl From<TokenStream> for RsSnippet {
90 fn from(tokens: TokenStream) -> Self {
91 RsSnippet { features: BTreeSet::new(), tokens }
92 }
93}
94
Michael Forsterbee84482021-10-13 08:35:38 +000095/// If we know the original C++ function is codegenned and already compatible
96/// with `extern "C"` calling convention we skip creating/calling the C++ thunk
97/// since we can call the original C++ directly.
Marcel Hlopko3164eee2021-08-24 20:09:22 +000098fn can_skip_cc_thunk(func: &Func) -> bool {
Michael Forsterbee84482021-10-13 08:35:38 +000099 // Inline functions may not be codegenned in the C++ library since Clang doesn't
100 // know if Rust calls the function or not. Therefore in order to make inline
101 // functions callable from Rust we need to generate a C++ file that defines
102 // a thunk that delegates to the original inline function. When compiled,
103 // Clang will emit code for this thunk and Rust code will call the
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000104 // thunk when the user wants to call the original inline function.
105 //
Michael Forsterbee84482021-10-13 08:35:38 +0000106 // This is not great runtime-performance-wise in regular builds (inline function
107 // will not be inlined, there will always be a function call), but it is
108 // correct. ThinLTO builds will be able to see through the thunk and inline
109 // code across the language boundary. For non-ThinLTO builds we plan to
110 // implement <internal link> which removes the runtime performance overhead.
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000111 !func.is_inline
112}
113
Michael Forstered642022021-10-04 09:48:25 +0000114/// Generates Rust source code for a given `Func`.
115///
116/// Returns the generated function and the thunk as a tuple.
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000117fn generate_func(func: &Func) -> Result<(RsSnippet, RsSnippet)> {
Michael Forstered642022021-10-04 09:48:25 +0000118 let mangled_name = &func.mangled_name;
119 let ident = make_ident(&func.identifier.identifier);
120 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
Michael Forster409d9412021-10-07 08:35:29 +0000121 let doc_comment = generate_doc_comment(&func.doc_comment);
Michael Forstered642022021-10-04 09:48:25 +0000122 // TODO(hlopko): do not emit `-> ()` when return type is void, it's implicit.
123 let return_type_name = format_rs_type(&func.return_type.rs_type)?;
124
125 let param_idents =
126 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
127
128 let param_types =
129 func.params.iter().map(|p| format_rs_type(&p.type_.rs_type)).collect::<Result<Vec<_>>>()?;
130
131 let api_func = quote! {
Michael Forster409d9412021-10-07 08:35:29 +0000132 #doc_comment
Michael Forstered642022021-10-04 09:48:25 +0000133 #[inline(always)]
134 pub fn #ident( #( #param_idents: #param_types ),* ) -> #return_type_name {
135 unsafe { crate::detail::#thunk_ident( #( #param_idents ),* ) }
136 }
137 };
138
139 let thunk_attr = if can_skip_cc_thunk(func) {
140 quote! {#[link_name = #mangled_name]}
141 } else {
142 quote! {}
143 };
144
145 let thunk = quote! {
146 #thunk_attr
147 pub(crate) fn #thunk_ident( #( #param_idents: #param_types ),* ) -> #return_type_name ;
148 };
149
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000150 Ok((api_func.into(), thunk.into()))
Michael Forstered642022021-10-04 09:48:25 +0000151}
152
Michael Forstercc5941a2021-10-07 07:12:24 +0000153fn generate_doc_comment(comment: &Option<String>) -> TokenStream {
154 match comment {
Michael Forster028800b2021-10-05 12:39:59 +0000155 Some(text) => {
156 let doc = format!(" {}", text.replace("\n", "\n "));
157 quote! {#[doc=#doc]}
158 }
159 None => quote! {},
Michael Forstercc5941a2021-10-07 07:12:24 +0000160 }
161}
Michael Forsterbee84482021-10-13 08:35:38 +0000162/// Generates Rust source code for a given `Record` and associated assertions as
163/// a tuple.
Michael Forsterdb8101a2021-10-08 06:56:03 +0000164fn generate_record(record: &Record) -> Result<(RsSnippet, RsSnippet)> {
Michael Forstercc5941a2021-10-07 07:12:24 +0000165 let ident = make_ident(&record.identifier.identifier);
166 let doc_comment = generate_doc_comment(&record.doc_comment);
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000167 let field_idents =
168 record.fields.iter().map(|f| make_ident(&f.identifier.identifier)).collect_vec();
Michael Forstercc5941a2021-10-07 07:12:24 +0000169 let field_doc_coments =
170 record.fields.iter().map(|f| generate_doc_comment(&f.doc_comment)).collect_vec();
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000171 let field_types = record
172 .fields
173 .iter()
174 .map(|f| format_rs_type(&f.type_.rs_type))
175 .collect::<Result<Vec<_>>>()?;
Googlerec589eb2021-09-17 07:45:39 +0000176 let field_accesses = record
177 .fields
178 .iter()
179 .map(|f| {
180 if f.access == AccessSpecifier::Public {
181 quote! { pub }
182 } else {
183 quote! {}
184 }
185 })
186 .collect_vec();
Googlerec648ff2021-09-23 07:19:53 +0000187 let size = record.size;
188 let alignment = record.alignment;
Googleraaa0a532021-10-01 09:11:27 +0000189 let field_assertions =
190 record.fields.iter().zip(field_idents.iter()).map(|(field, field_ident)| {
191 let offset = field.offset;
192 quote! {
193 // The IR contains the offset in bits, while offset_of!()
194 // returns the offset in bytes, so we need to convert.
195 const_assert_eq!(offset_of!(#ident, #field_ident) * 8, #offset);
196 }
197 });
Michael Forsterdb8101a2021-10-08 06:56:03 +0000198 let mut record_features = BTreeSet::new();
199 let mut assertion_features = BTreeSet::new();
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000200
201 // TODO(mboehme): For the time being, we're using unstable features to
202 // be able to use offset_of!() in static assertions. This is fine for a
203 // prototype, but longer-term we want to either get those features
204 // stabilized or find an alternative. For more details, see
205 // b/200120034#comment15
Michael Forsterdb8101a2021-10-08 06:56:03 +0000206 assertion_features.insert(make_ident("const_ptr_offset_from"));
207 assertion_features.insert(make_ident("const_maybe_uninit_as_ptr"));
208 assertion_features.insert(make_ident("const_raw_ptr_deref"));
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000209
210 let derives = generate_copy_derives(record);
Devin Jeanpierre9227d2c2021-10-06 12:26:05 +0000211 let derives = if derives.is_empty() {
212 quote! {}
213 } else {
214 quote! {#[derive( #(#derives),* )]}
215 };
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000216 let unpin_impl;
217 if record.is_trivial_abi {
218 unpin_impl = quote! {};
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000219 } else {
Michael Forsterbee84482021-10-13 08:35:38 +0000220 // negative_impls are necessary for universal initialization due to Rust's
221 // coherence rules: PhantomPinned isn't enough to prove to Rust that a
222 // blanket impl that requires Unpin doesn't apply. See http://<internal link>=h.f6jp8ifzgt3n
Michael Forsterdb8101a2021-10-08 06:56:03 +0000223 record_features.insert(make_ident("negative_impls"));
224 unpin_impl = quote! {
225 __NEWLINE__ __NEWLINE__
226 impl !Unpin for #ident {}
227 };
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000228 }
229
Michael Forsterdb8101a2021-10-08 06:56:03 +0000230 let record_tokens = quote! {
Michael Forster028800b2021-10-05 12:39:59 +0000231 #doc_comment
Devin Jeanpierre9227d2c2021-10-06 12:26:05 +0000232 #derives
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000233 #[repr(C)]
234 pub struct #ident {
Michael Forstercc5941a2021-10-07 07:12:24 +0000235 #( #field_doc_coments #field_accesses #field_idents: #field_types, )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000236 }
Googlerec648ff2021-09-23 07:19:53 +0000237
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000238 #unpin_impl
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000239 };
240
Michael Forsterdb8101a2021-10-08 06:56:03 +0000241 let assertion_tokens = quote! {
242 const_assert_eq!(std::mem::size_of::<#ident>(), #size);
243 const_assert_eq!(std::mem::align_of::<#ident>(), #alignment);
244 #( #field_assertions )*
245 };
246
247 Ok((
248 RsSnippet { features: record_features, tokens: record_tokens },
249 RsSnippet { features: assertion_features, tokens: assertion_tokens },
250 ))
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000251}
252
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000253fn generate_copy_derives(record: &Record) -> Vec<Ident> {
254 if record.is_trivial_abi
255 && record.copy_constructor.access == ir::AccessSpecifier::Public
256 && record.copy_constructor.definition == SpecialMemberDefinition::Trivial
257 {
258 // TODO(b/202258760): Make `Copy` inclusion configurable.
259 vec![make_ident("Clone"), make_ident("Copy")]
260 } else {
261 vec![]
262 }
263}
264
Michael Forster523dbd42021-10-12 11:05:44 +0000265/// Generates Rust source code for a given `UnsupportedItem`.
266fn generate_unsupported(item: &UnsupportedItem) -> Result<TokenStream> {
Michael Forster6a184ad2021-10-12 13:04:05 +0000267 let message = format!(
268 // TODO(forster): The "google3" prefix should probably come from a command line argument.
Michael Forster07a38332021-10-13 07:15:45 +0000269 // TODO(forster): Consider linking to the symbol instead of to the line number to avoid
270 // wrong links while generated files have not caught up.
271 "google3/{};l={}\nError while generating bindings for item '{}':\n{}",
Michael Forster6a184ad2021-10-12 13:04:05 +0000272 &item.source_loc.filename,
273 &item.source_loc.line,
Michael Forster6a184ad2021-10-12 13:04:05 +0000274 &item.name,
275 &item.message
276 );
Michael Forster523dbd42021-10-12 11:05:44 +0000277 Ok(quote! { __COMMENT__ #message })
278}
279
Michael Forsterf1dce422021-10-13 09:50:16 +0000280/// Generates Rust source code for a given `Comment`.
281fn generate_comment(comment: &Comment) -> Result<TokenStream> {
282 let text = &comment.text;
283 Ok(quote! { __COMMENT__ #text })
284}
285
Marcel Hlopko45fba972021-08-23 19:52:20 +0000286fn generate_rs_api(ir: &IR) -> Result<String> {
Michael Forstered642022021-10-04 09:48:25 +0000287 let mut items = vec![];
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000288 let mut thunks = vec![];
Michael Forsterdb8101a2021-10-08 06:56:03 +0000289 let mut assertions = vec![];
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000290
Michael Forsterbee84482021-10-13 08:35:38 +0000291 // TODO(jeanpierreda): Delete has_record, either in favor of using RsSnippet, or not
292 // having uses. See https://chat.google.com/room/AAAAnQmj8Qs/6QbkSvWcfhA
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000293 let mut has_record = false;
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000294 let mut features = BTreeSet::new();
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000295
Michael Forstered642022021-10-04 09:48:25 +0000296 for item in &ir.items {
297 match item {
298 Item::Func(func) => {
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000299 let (snippet, thunk) = generate_func(func)?;
300 features.extend(snippet.features);
301 features.extend(thunk.features);
302 items.push(snippet.tokens);
303 thunks.push(thunk.tokens);
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000304 }
Michael Forstered642022021-10-04 09:48:25 +0000305 Item::Record(record) => {
Michael Forsterdb8101a2021-10-08 06:56:03 +0000306 let (snippet, assertions_snippet) = generate_record(record)?;
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000307 features.extend(snippet.features);
Michael Forsterdb8101a2021-10-08 06:56:03 +0000308 features.extend(assertions_snippet.features);
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000309 items.push(snippet.tokens);
Michael Forsterdb8101a2021-10-08 06:56:03 +0000310 assertions.push(assertions_snippet.tokens);
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000311 has_record = true;
Michael Forstered642022021-10-04 09:48:25 +0000312 }
Michael Forster523dbd42021-10-12 11:05:44 +0000313 Item::UnsupportedItem(unsupported) => items.push(generate_unsupported(unsupported)?),
Michael Forsterf1dce422021-10-13 09:50:16 +0000314 Item::Comment(comment) => items.push(generate_comment(comment)?),
Michael Forstered642022021-10-04 09:48:25 +0000315 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000316 }
317
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000318 let mod_detail = if thunks.is_empty() {
319 quote! {}
320 } else {
321 quote! {
322 mod detail {
323 extern "C" {
324 #( #thunks )*
325 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000326 }
327 }
328 };
329
Devin Jeanpierreea700d32021-10-06 11:33:56 +0000330 let imports = if has_record {
Googlerec648ff2021-09-23 07:19:53 +0000331 quote! {
Googleraaa0a532021-10-01 09:11:27 +0000332 use memoffset_unstable_const::offset_of;
333 use static_assertions::const_assert_eq;
Googlerec648ff2021-09-23 07:19:53 +0000334 }
Michael Forstered642022021-10-04 09:48:25 +0000335 } else {
336 quote! {}
Googlerec648ff2021-09-23 07:19:53 +0000337 };
338
Devin Jeanpierre273eeae2021-10-06 13:29:35 +0000339 let features = if features.is_empty() {
340 quote! {}
341 } else {
342 quote! {
343 #![feature( #(#features),* )]
344 }
345 };
346
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000347 let result = quote! {
Michael Forsterdb8101a2021-10-08 06:56:03 +0000348 #features __NEWLINE__ __NEWLINE__
349 #imports __NEWLINE__ __NEWLINE__
Googlerec648ff2021-09-23 07:19:53 +0000350
Michael Forsterdb8101a2021-10-08 06:56:03 +0000351 #( #items __NEWLINE__ __NEWLINE__ )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000352
Michael Forsterdb8101a2021-10-08 06:56:03 +0000353 #mod_detail __NEWLINE__ __NEWLINE__
354
355 #( #assertions __NEWLINE__ __NEWLINE__ )*
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000356 };
357
Michael Forsterdb8101a2021-10-08 06:56:03 +0000358 rustfmt(token_stream_printer::tokens_to_string(result)?)
Michael Forstere9c881c2021-10-01 09:38:19 +0000359}
360
361fn rustfmt(input: String) -> Result<String> {
Michael Forsterbee84482021-10-13 08:35:38 +0000362 // TODO(forster): This should use rustfmt as a library as soon as b/200503084 is
363 // fixed.
Michael Forstere9c881c2021-10-01 09:38:19 +0000364
365 let rustfmt = "third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt";
366
367 let mut child = Command::new(rustfmt)
Michael Forster028800b2021-10-05 12:39:59 +0000368 .args(&[
Michael Forster523dbd42021-10-12 11:05:44 +0000369 // TODO(forster): Add a way to specify this as a command line parameter.
Michael Forster028800b2021-10-05 12:39:59 +0000370 "--config-path=external/rustfmt/rustfmt.toml",
Michael Forster523dbd42021-10-12 11:05:44 +0000371 // We are representing doc comments as attributes in the token stream and use rustfmt
372 // to unpack them again.
Michael Forster028800b2021-10-05 12:39:59 +0000373 "--config=normalize_doc_attributes=true",
Michael Forster523dbd42021-10-12 11:05:44 +0000374 // We don't want rustfmt to reflow C++ doc comments, so we turn off wrapping globally
375 // and reflow generated comments manually.
376 "--config=wrap_comments=false",
Michael Forster028800b2021-10-05 12:39:59 +0000377 ])
Michael Forstere9c881c2021-10-01 09:38:19 +0000378 .stdin(Stdio::piped())
379 .stdout(Stdio::piped())
380 .spawn()
381 .expect(&format!("Failed to spawn rustfmt at '{}'", rustfmt));
382
383 let mut stdin = child.stdin.take().expect("Failed to open rustfmt stdin");
384 std::thread::spawn(move || {
385 stdin.write_all(input.as_bytes()).expect("Failed to write to rustfmt stdin");
386 });
387 let output = child.wait_with_output().expect("Failed to read rustfmt stdout");
388
Michael Forster136f0aa2021-10-01 12:43:14 +0000389 if !output.status.success() {
390 // The rustfmt error message has already been printed to stderr.
391 bail!("Unable to format output with rustfmt");
392 }
393
Michael Forstere9c881c2021-10-01 09:38:19 +0000394 Ok(String::from_utf8_lossy(&output.stdout).to_string())
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000395}
396
397fn make_ident(ident: &str) -> Ident {
398 format_ident!("{}", ident)
399}
400
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000401fn format_rs_type(ty: &ir::RsType) -> Result<TokenStream> {
402 let ptr_fragment = match ty.name.as_str() {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000403 "*mut" => Some(quote! {*mut}),
404 "*const" => Some(quote! {*const}),
405 _ => None,
406 };
407 match ptr_fragment {
408 Some(ptr_fragment) => {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000409 if ty.type_params.len() != 1 {
410 return Err(anyhow!(
411 "Invalid pointer type (need exactly 1 type parameter): {:?}",
412 ty
413 ));
414 }
415 let nested_type = format_rs_type(&ty.type_params[0])?;
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000416 Ok(quote! {#ptr_fragment #nested_type})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000417 }
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000418 None => {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000419 if ty.type_params.len() > 0 {
420 return Err(anyhow!("Type not yet supported: {:?}", ty));
421 }
Devin Jeanpierre35639a72021-10-08 11:39:59 +0000422 match ty.name.as_str() {
423 "()" => Ok(quote! {()}),
424 name => {
425 let ident = make_ident(name);
426 Ok(quote! {#ident})
427 }
428 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000429 }
430 }
431}
432
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000433fn format_cc_type(ty: &ir::CcType) -> Result<TokenStream> {
434 let const_fragment = if ty.is_const {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000435 quote! {const}
436 } else {
437 quote! {}
438 };
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000439 match ty.name.as_str() {
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000440 "*" => {
441 if ty.type_params.len() != 1 {
442 return Err(anyhow!(
443 "Invalid pointer type (need exactly 1 type parameter): {:?}",
444 ty
445 ));
446 }
447 assert_eq!(ty.type_params.len(), 1);
448 let nested_type = format_cc_type(&ty.type_params[0])?;
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000449 Ok(quote! {#nested_type * #const_fragment})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000450 }
451 ident => {
452 if ty.type_params.len() > 0 {
453 return Err(anyhow!("Type not yet supported: {:?}", ty));
454 }
455 let ident = make_ident(ident);
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000456 Ok(quote! {#ident #const_fragment})
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000457 }
458 }
459}
460
Googler5ea88642021-09-29 08:05:59 +0000461fn cc_struct_layout_assertion(record: &Record) -> TokenStream {
462 let record_ident = make_ident(&record.identifier.identifier);
463 let size = Literal::usize_unsuffixed(record.size);
464 let alignment = Literal::usize_unsuffixed(record.alignment);
465 let field_assertions = record.fields.iter().map(|field| {
466 let field_ident = make_ident(&field.identifier.identifier);
467 let offset = Literal::usize_unsuffixed(field.offset);
468 // The IR contains the offset in bits, while C++'s offsetof()
469 // returns the offset in bytes, so we need to convert.
470 quote! {
471 static_assert(offsetof(#record_ident, #field_ident) * 8 == #offset);
472 }
473 });
474 quote! {
475 static_assert(sizeof(#record_ident) == #size);
476 static_assert(alignof(#record_ident) == #alignment);
477 #( #field_assertions )*
478 }
479}
480
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000481fn generate_rs_api_impl(ir: &IR) -> Result<String> {
Michael Forsterbee84482021-10-13 08:35:38 +0000482 // This function uses quote! to generate C++ source code out of convenience.
483 // This is a bold idea so we have to continously evaluate if it still makes
484 // sense or the cost of working around differences in Rust and C++ tokens is
485 // greather than the value added.
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000486 //
Michael Forsterbee84482021-10-13 08:35:38 +0000487 // See rs_bindings_from_cc/
488 // token_stream_printer.rs for a list of supported placeholders.
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000489 let mut thunks = vec![];
Michael Forster7ef80732021-10-01 18:12:19 +0000490 for func in ir.functions() {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000491 if can_skip_cc_thunk(&func) {
492 continue;
493 }
494
495 let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
496 let ident = make_ident(&func.identifier.identifier);
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000497 let return_type_name = format_cc_type(&func.return_type.cc_type)?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000498
499 let param_idents =
500 func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
501
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000502 let param_types = func
503 .params
504 .iter()
505 .map(|p| format_cc_type(&p.type_.cc_type))
506 .collect::<Result<Vec<_>>>()?;
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000507
508 thunks.push(quote! {
509 extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
510 return #ident( #( #param_idents ),* );
511 }
512 });
513 }
514
Michael Forster7ef80732021-10-01 18:12:19 +0000515 let layout_assertions = ir.records().map(cc_struct_layout_assertion);
Googler5ea88642021-09-29 08:05:59 +0000516
Michael Forster7ef80732021-10-01 18:12:19 +0000517 let standard_headers =
518 if ir.records().next().is_none() { vec![] } else { vec![make_ident("cstddef")] };
Googler5ea88642021-09-29 08:05:59 +0000519
Michael Forsterbee84482021-10-13 08:35:38 +0000520 // In order to generate C++ thunk in all the cases Clang needs to be able to
521 // access declarations from public headers of the C++ library.
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000522 let includes = ir.used_headers.iter().map(|i| &i.name);
523
524 let result = quote! {
Googler5ea88642021-09-29 08:05:59 +0000525 #( __HASH_TOKEN__ include <#standard_headers> __NEWLINE__)*
Michael Forsterdb8101a2021-10-08 06:56:03 +0000526 #( __HASH_TOKEN__ include #includes __NEWLINE__)* __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000527
Michael Forsterdb8101a2021-10-08 06:56:03 +0000528 #( #thunks )* __NEWLINE__ __NEWLINE__
Googler5ea88642021-09-29 08:05:59 +0000529
Michael Forsterdb8101a2021-10-08 06:56:03 +0000530 #( #layout_assertions __NEWLINE__ __NEWLINE__ )*
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000531
532 // To satisfy http://cs/symbol:devtools.metadata.Presubmit.CheckTerminatingNewline check.
533 __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000534 };
535
Michael Forsterdb8101a2021-10-08 06:56:03 +0000536 token_stream_printer::tokens_to_string(result)
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000537}
538
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000539#[cfg(test)]
540mod tests {
Michael Forstere9c881c2021-10-01 09:38:19 +0000541 use crate::rustfmt;
542
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000543 use super::Result;
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000544 use super::{generate_copy_derives, generate_rs_api, generate_rs_api_impl};
Michael Forstered642022021-10-04 09:48:25 +0000545 use anyhow::anyhow;
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000546 use ir::*;
Michael Forster028800b2021-10-05 12:39:59 +0000547 use ir_testing::{
548 ir_func, ir_id, ir_int, ir_int_param, ir_items, ir_public_trivial_special, ir_record,
549 };
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000550 use quote::quote;
Michael Forsterdb8101a2021-10-08 06:56:03 +0000551 use token_stream_printer::tokens_to_string;
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000552
553 #[test]
Marcel Hlopko45fba972021-08-23 19:52:20 +0000554 fn test_simple_function() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000555 let ir = ir_items(vec![Item::Func(Func {
556 identifier: ir_id("add"),
557 mangled_name: "_Z3Addii".to_string(),
Michael Forster409d9412021-10-07 08:35:29 +0000558 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000559 return_type: ir_int(),
560 params: vec![ir_int_param("a"), ir_int_param("b")],
561 is_inline: false,
562 })]);
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000563 assert_eq!(
Marcel Hlopko45fba972021-08-23 19:52:20 +0000564 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000565 rustfmt(tokens_to_string(quote! {
566 #[inline(always)]
567 pub fn add(a: i32, b: i32) -> i32 {
568 unsafe { crate::detail::__rust_thunk__add(a, b) }
569 } __NEWLINE__ __NEWLINE__
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000570
Michael Forsterdb8101a2021-10-08 06:56:03 +0000571 mod detail {
572 extern "C" {
573 #[link_name = "_Z3Addii"]
574 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
575 } // extern
576 } // mod detail
577 })?)?
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000578 );
Michael Forsterdb8101a2021-10-08 06:56:03 +0000579
580 let rs_api = generate_rs_api_impl(&ir)?;
581 assert_eq!(rs_api.trim(), "");
582 assert!(rs_api.ends_with("\n"));
583
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000584 Ok(())
585 }
586
587 #[test]
588 fn test_inline_function() -> Result<()> {
589 let ir = IR {
590 used_headers: vec![
591 HeaderName { name: "foo/bar.h".to_string() },
592 HeaderName { name: "foo/baz.h".to_string() },
593 ],
Michael Forster7ef80732021-10-01 18:12:19 +0000594 items: vec![Item::Func(Func {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000595 identifier: Identifier { identifier: "add".to_string() },
596 mangled_name: "_Z3Addii".to_string(),
Michael Forster409d9412021-10-07 08:35:29 +0000597 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000598 return_type: ir_int(),
599 params: vec![ir_int_param("a"), ir_int_param("b")],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000600 is_inline: true,
Michael Forster7ef80732021-10-01 18:12:19 +0000601 })],
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000602 };
603
604 assert_eq!(
605 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000606 rustfmt(tokens_to_string(quote! {
607 #[inline(always)]
608 pub fn add(a: i32, b: i32) -> i32 {
609 unsafe { crate::detail::__rust_thunk__add(a, b) }
610 } __NEWLINE__ __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000611
Michael Forsterdb8101a2021-10-08 06:56:03 +0000612 mod detail {
613 extern "C" {
614 pub(crate) fn __rust_thunk__add(a: i32, b: i32) -> i32;
615 } // extern
616 } // mod detail
617 })?)?
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000618 );
619
620 assert_eq!(
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000621 generate_rs_api_impl(&ir)?.trim(),
Michael Forsterdb8101a2021-10-08 06:56:03 +0000622 tokens_to_string(quote! {
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000623 __HASH_TOKEN__ include "foo/bar.h" __NEWLINE__
Michael Forsterdb8101a2021-10-08 06:56:03 +0000624 __HASH_TOKEN__ include "foo/baz.h" __NEWLINE__ __NEWLINE__
Marcel Hlopko3164eee2021-08-24 20:09:22 +0000625
626 extern "C" int __rust_thunk__add(int a, int b) {
627 return add(a, b);
628 }
629 })?
630 );
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000631 Ok(())
632 }
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000633
634 #[test]
635 fn test_simple_struct() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000636 let ir = ir_items(vec![Item::Record(Record {
637 identifier: ir_id("SomeStruct"),
Michael Forster028800b2021-10-05 12:39:59 +0000638 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000639 fields: vec![
640 Field {
641 identifier: ir_id("public_int"),
Michael Forstercc5941a2021-10-07 07:12:24 +0000642 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000643 type_: ir_int(),
644 access: AccessSpecifier::Public,
645 offset: 0,
646 },
647 Field {
648 identifier: ir_id("protected_int"),
Michael Forstercc5941a2021-10-07 07:12:24 +0000649 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000650 type_: ir_int(),
651 access: AccessSpecifier::Protected,
652 offset: 32,
653 },
654 Field {
655 identifier: ir_id("private_int"),
Michael Forstercc5941a2021-10-07 07:12:24 +0000656 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000657 type_: ir_int(),
658 access: AccessSpecifier::Private,
659 offset: 64,
660 },
661 ],
662 size: 12,
663 alignment: 4,
Michael Forster028800b2021-10-05 12:39:59 +0000664 copy_constructor: ir_public_trivial_special(),
665 move_constructor: ir_public_trivial_special(),
666 destructor: ir_public_trivial_special(),
Michael Forster3a4615d2021-10-05 10:40:36 +0000667 is_trivial_abi: true,
668 })]);
Michael Forster028800b2021-10-05 12:39:59 +0000669
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000670 assert_eq!(
671 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000672 rustfmt(tokens_to_string(quote! {
673 #![feature(const_maybe_uninit_as_ptr, const_ptr_offset_from, const_raw_ptr_deref)] __NEWLINE__ __NEWLINE__
Googlerec648ff2021-09-23 07:19:53 +0000674
Michael Forsterdb8101a2021-10-08 06:56:03 +0000675 use memoffset_unstable_const::offset_of;
676 use static_assertions::const_assert_eq; __NEWLINE__ __NEWLINE__
Michael Forstere9c881c2021-10-01 09:38:19 +0000677
Michael Forsterdb8101a2021-10-08 06:56:03 +0000678 #[derive(Clone, Copy)]
679 #[repr(C)]
680 pub struct SomeStruct {
681 pub public_int: i32,
682 protected_int: i32,
683 private_int: i32,
684 } __NEWLINE__ __NEWLINE__
685
686 const_assert_eq!(std::mem::size_of::<SomeStruct>(), 12usize);
687 const_assert_eq!(std::mem::align_of::<SomeStruct>(), 4usize);
688 const_assert_eq!(offset_of!(SomeStruct, public_int) * 8, 0usize);
689 const_assert_eq!(offset_of!(SomeStruct, protected_int) * 8, 32usize);
690 const_assert_eq!(offset_of!(SomeStruct, private_int) * 8, 64usize);
691 })?)?
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000692 );
Googler5ea88642021-09-29 08:05:59 +0000693 assert_eq!(
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000694 generate_rs_api_impl(&ir)?.trim(),
Michael Forsterdb8101a2021-10-08 06:56:03 +0000695 tokens_to_string(quote! {
696 __HASH_TOKEN__ include <cstddef> __NEWLINE__ __NEWLINE__ __NEWLINE__ __NEWLINE__
Googler5ea88642021-09-29 08:05:59 +0000697 static_assert(sizeof(SomeStruct) == 12);
698 static_assert(alignof(SomeStruct) == 4);
699 static_assert(offsetof(SomeStruct, public_int) * 8 == 0);
700 static_assert(offsetof(SomeStruct, protected_int) * 8 == 32);
701 static_assert(offsetof(SomeStruct, private_int) * 8 == 64);
702 })?
703 );
Marcel Hlopkob4b28742021-09-15 12:45:20 +0000704 Ok(())
705 }
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000706
Devin Jeanpierre2ed14ec2021-10-06 11:32:19 +0000707 fn unwrapped_ir_record(name: &str) -> Record {
708 match ir_record(name) {
709 Item::Record(r) => r,
710 _ => unreachable!(),
711 }
712 }
713
714 #[test]
715 fn test_copy_derives() {
716 let record = unwrapped_ir_record("S");
717 assert_eq!(generate_copy_derives(&record), &["Clone", "Copy"]);
718 }
719
720 #[test]
721 fn test_copy_derives_not_is_trivial_abi() {
722 let mut record = unwrapped_ir_record("S");
723 record.is_trivial_abi = false;
724 assert_eq!(generate_copy_derives(&record), &[""; 0]);
725 }
726
727 #[test]
728 fn test_copy_derives_ctor_nonpublic() {
729 let mut record = unwrapped_ir_record("S");
730 for access in [ir::AccessSpecifier::Protected, ir::AccessSpecifier::Private] {
731 record.copy_constructor.access = access;
732 assert_eq!(generate_copy_derives(&record), &[""; 0]);
733 }
734 }
735
736 #[test]
737 fn test_copy_derives_ctor_deleted() {
738 let mut record = unwrapped_ir_record("S");
739 record.copy_constructor.definition = ir::SpecialMemberDefinition::Deleted;
740 assert_eq!(generate_copy_derives(&record), &[""; 0]);
741 }
742
743 #[test]
744 fn test_copy_derives_ctor_nontrivial() {
745 let mut record = unwrapped_ir_record("S");
746 record.copy_constructor.definition = ir::SpecialMemberDefinition::Nontrivial;
747 assert_eq!(generate_copy_derives(&record), &[""; 0]);
748 }
749
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000750 #[test]
751 fn test_ptr_func() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000752 let ir = ir_items(vec![Item::Func(Func {
753 identifier: Identifier { identifier: "Deref".to_string() },
754 mangled_name: "_Z5DerefPKPi".to_string(),
Michael Forster409d9412021-10-07 08:35:29 +0000755 doc_comment: None,
Michael Forster3a4615d2021-10-05 10:40:36 +0000756 return_type: MappedType {
757 rs_type: RsType {
758 name: "*mut".to_string(),
759 type_params: vec![RsType { name: "i32".to_string(), type_params: vec![] }],
760 },
761 cc_type: CcType {
762 name: "*".to_string(),
763 is_const: false,
764 type_params: vec![CcType {
765 name: "int".to_string(),
766 is_const: false,
767 type_params: vec![],
768 }],
769 },
770 },
771 params: vec![FuncParam {
772 identifier: Identifier { identifier: "p".to_string() },
773 type_: MappedType {
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000774 rs_type: RsType {
Michael Forster3a4615d2021-10-05 10:40:36 +0000775 name: "*const".to_string(),
776 type_params: vec![RsType {
777 name: "*mut".to_string(),
778 type_params: vec![RsType {
779 name: "i32".to_string(),
780 type_params: vec![],
781 }],
782 }],
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000783 },
784 cc_type: CcType {
785 name: "*".to_string(),
786 is_const: false,
787 type_params: vec![CcType {
Michael Forster3a4615d2021-10-05 10:40:36 +0000788 name: "*".to_string(),
789 is_const: true,
790 type_params: vec![CcType {
791 name: "int".to_string(),
792 is_const: false,
793 type_params: vec![],
794 }],
Devin Jeanpierre09c6f452021-09-29 07:34:24 +0000795 }],
796 },
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000797 },
Michael Forster3a4615d2021-10-05 10:40:36 +0000798 }],
799 is_inline: true,
800 })]);
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000801 assert_eq!(
802 generate_rs_api(&ir)?,
Michael Forsterdb8101a2021-10-08 06:56:03 +0000803 rustfmt(tokens_to_string(quote! {
804 #[inline(always)]
805 pub fn Deref(p: *const *mut i32) -> *mut i32 {
806 unsafe { crate::detail::__rust_thunk__Deref(p) }
807 } __NEWLINE__ __NEWLINE__
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000808
Michael Forsterdb8101a2021-10-08 06:56:03 +0000809 mod detail {
810 extern "C" {
811 pub(crate) fn __rust_thunk__Deref(p: *const *mut i32) -> *mut i32;
812 } // extern
813 } // mod detail
814 })?)?
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000815 );
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000816
817 assert_eq!(
Marcel Hlopkoc6b726c2021-10-07 06:53:09 +0000818 generate_rs_api_impl(&ir)?.trim(),
Michael Forsterdb8101a2021-10-08 06:56:03 +0000819 tokens_to_string(quote! {
Devin Jeanpierre184f9ac2021-09-17 13:47:03 +0000820 extern "C" int* __rust_thunk__Deref(int* const * p) {
821 return Deref(p);
822 }
823 })?
824 );
Devin Jeanpierre7a7328e2021-09-17 07:10:08 +0000825 Ok(())
826 }
Michael Forstered642022021-10-04 09:48:25 +0000827
828 #[test]
829 fn test_item_order() -> Result<()> {
Michael Forster3a4615d2021-10-05 10:40:36 +0000830 let ir = ir_items(vec![
831 ir_func("first_func"),
832 ir_record("FirstStruct"),
833 ir_func("second_func"),
834 ir_record("SecondStruct"),
835 ]);
Michael Forstered642022021-10-04 09:48:25 +0000836
837 let rs_api = generate_rs_api(&ir)?;
838
839 let idx = |s: &str| rs_api.find(s).ok_or(anyhow!("'{}' missing", s));
840
841 let f1 = idx("fn first_func")?;
842 let f2 = idx("fn second_func")?;
843 let s1 = idx("struct FirstStruct")?;
844 let s2 = idx("struct SecondStruct")?;
845 let t1 = idx("fn __rust_thunk__first_func")?;
846 let t2 = idx("fn __rust_thunk__second_func")?;
847
848 assert!(f1 < s1);
849 assert!(s1 < f2);
850 assert!(f2 < s2);
851 assert!(s2 < t1);
852 assert!(t1 < t2);
853
854 Ok(())
855 }
Michael Forster028800b2021-10-05 12:39:59 +0000856
857 #[test]
Michael Forster409d9412021-10-07 08:35:29 +0000858 fn test_doc_comment_func() -> Result<()> {
859 let ir = IR {
860 used_headers: vec![],
861 items: vec![Item::Func(Func {
862 identifier: ir_id("func"),
863 mangled_name: "foo".to_string(),
864 doc_comment: Some("Doc Comment".to_string()),
865 return_type: ir_int(),
866 is_inline: true,
867 params: vec![],
868 })],
869 };
870
871 assert!(
872 generate_rs_api(&ir)?.contains("/// Doc Comment\n#[inline(always)]\npub fn func"),
873 "func doc comment missing"
874 );
875
876 Ok(())
877 }
878
879 #[test]
880 fn test_doc_comment_record() -> Result<()> {
Michael Forster028800b2021-10-05 12:39:59 +0000881 let ir = IR {
882 used_headers: vec![],
883 items: vec![Item::Record(Record {
884 identifier: ir_id("SomeStruct"),
885 doc_comment: Some("Doc Comment\n\n * with bullet".to_string()),
886 alignment: 0,
887 size: 0,
Michael Forstercc5941a2021-10-07 07:12:24 +0000888 fields: vec![Field {
889 identifier: ir_id("field"),
890 doc_comment: Some("Field doc".to_string()),
891 type_: ir_int(),
892 access: AccessSpecifier::Public,
893 offset: 0,
894 }],
Michael Forster028800b2021-10-05 12:39:59 +0000895 copy_constructor: ir_public_trivial_special(),
896 move_constructor: ir_public_trivial_special(),
897 destructor: ir_public_trivial_special(),
898 is_trivial_abi: true,
899 })],
900 };
901
Michael Forstercc5941a2021-10-07 07:12:24 +0000902 let rs_api = generate_rs_api(&ir)?;
903 assert!(
904 rs_api.contains("/// Doc Comment\n///\n/// * with bullet\n"),
905 "struct doc comment missing"
906 );
907 assert!(rs_api.contains("/// Field doc\n"), "field doc comment missing");
Michael Forster028800b2021-10-05 12:39:59 +0000908
909 Ok(())
910 }
Marcel Hlopko42abfc82021-08-09 07:03:17 +0000911}