autocxx_engine/
known_types.rs

1// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::types::{make_ident, QualifiedName};
10use indexmap::map::IndexMap as HashMap;
11use indoc::indoc;
12use once_cell::sync::OnceCell;
13use syn::{parse_quote, TypePath};
14
15/// The behavior of the type.
16#[derive(Debug)]
17enum Behavior {
18    CxxContainerPtr,
19    CxxContainerVector,
20    CxxString,
21    RustStr,
22    RustString,
23    RustByValue,
24    CByValue,
25    CByValueVecSafe,
26    CVariableLengthByValue,
27    CVoid,
28    CChar16,
29    RustContainerByValueSafe,
30}
31
32/// Details about known special types, mostly primitives.
33#[derive(Debug)]
34struct TypeDetails {
35    /// The name used by cxx (in Rust code) for this type.
36    rs_name: String,
37    /// C++ equivalent name for a Rust type.
38    cpp_name: String,
39    /// The behavior of the type.
40    behavior: Behavior,
41    /// Any extra non-canonical names
42    extra_non_canonical_name: Option<String>,
43    has_const_copy_constructor: bool,
44    has_move_constructor: bool,
45}
46
47impl TypeDetails {
48    fn new(
49        rs_name: impl Into<String>,
50        cpp_name: impl Into<String>,
51        behavior: Behavior,
52        extra_non_canonical_name: Option<String>,
53        has_const_copy_constructor: bool,
54        has_move_constructor: bool,
55    ) -> Self {
56        TypeDetails {
57            rs_name: rs_name.into(),
58            cpp_name: cpp_name.into(),
59            behavior,
60            extra_non_canonical_name,
61            has_const_copy_constructor,
62            has_move_constructor,
63        }
64    }
65
66    /// Whether and how to include this in the prelude given to bindgen.
67    fn get_prelude_entry(&self) -> Option<String> {
68        match self.behavior {
69            Behavior::RustString
70            | Behavior::RustStr
71            | Behavior::CxxString
72            | Behavior::CxxContainerPtr
73            | Behavior::CxxContainerVector
74            | Behavior::RustContainerByValueSafe => {
75                let tn = QualifiedName::new_from_cpp_name(&self.rs_name);
76                let cxx_name = tn.get_final_item();
77                let (templating, payload) = match self.behavior {
78                    Behavior::CxxContainerPtr
79                    | Behavior::CxxContainerVector
80                    | Behavior::RustContainerByValueSafe => ("template<typename T> ", "T* ptr"),
81                    _ => ("", "char* ptr"),
82                };
83                Some(format!(
84                    indoc! {"
85                    /**
86                    * <div rustbindgen=\"true\" replaces=\"{}\"></div>
87                    */
88                    {}class {} {{
89                        {};
90                    }};
91                    "},
92                    self.cpp_name, templating, cxx_name, payload
93                ))
94            }
95            _ => None,
96        }
97    }
98
99    fn to_type_path(&self) -> TypePath {
100        let mut segs = self.rs_name.split("::").peekable();
101        if segs.peek().map(|seg| seg.is_empty()).unwrap_or_default() {
102            segs.next();
103            let segs = segs.map(make_ident);
104            parse_quote! {
105                ::#(#segs)::*
106            }
107        } else {
108            let segs = segs.map(make_ident);
109            parse_quote! {
110                #(#segs)::*
111            }
112        }
113    }
114
115    fn to_typename(&self) -> QualifiedName {
116        QualifiedName::new_from_cpp_name(&self.rs_name)
117    }
118
119    fn get_generic_behavior(&self) -> CxxGenericType {
120        match self.behavior {
121            Behavior::CxxContainerPtr => CxxGenericType::CppPtr,
122            Behavior::CxxContainerVector => CxxGenericType::CppVector,
123            Behavior::RustContainerByValueSafe => CxxGenericType::Rust,
124            _ => CxxGenericType::Not,
125        }
126    }
127}
128
129/// Database of known types.
130#[derive(Default)]
131pub(crate) struct TypeDatabase {
132    by_rs_name: HashMap<QualifiedName, TypeDetails>,
133    canonical_names: HashMap<QualifiedName, QualifiedName>,
134}
135
136/// Returns a database of known types.
137pub(crate) fn known_types() -> &'static TypeDatabase {
138    static KNOWN_TYPES: OnceCell<TypeDatabase> = OnceCell::new();
139    KNOWN_TYPES.get_or_init(create_type_database)
140}
141
142/// The type of payload that a cxx generic can contain.
143#[derive(PartialEq, Eq, Clone, Copy)]
144pub enum CxxGenericType {
145    /// Not a generic at all
146    Not,
147    /// Some generic like cxx::UniquePtr where the contents must be a
148    /// complete type.
149    CppPtr,
150    /// Some generic like cxx::Vector where the contents must be a
151    /// complete type, and some types of int are allowed too.
152    CppVector,
153    /// Some generic like rust::Box where forward declarations are OK
154    Rust,
155}
156
157pub struct KnownTypeConstructorDetails {
158    pub has_move_constructor: bool,
159    pub has_const_copy_constructor: bool,
160}
161
162impl TypeDatabase {
163    fn get(&self, ty: &QualifiedName) -> Option<&TypeDetails> {
164        // The following line is important. It says that
165        // when we encounter something like 'std::unique_ptr'
166        // in the bindgen-generated bindings, we'll immediately
167        // start to refer to that as 'UniquePtr' henceforth.
168        let canonical_name = self.canonical_names.get(ty).unwrap_or(ty);
169        self.by_rs_name.get(canonical_name)
170    }
171
172    /// Prelude of C++ for squirting into bindgen. This configures
173    /// bindgen to output simpler types to replace some STL types
174    /// that bindgen just can't cope with. Although we then replace
175    /// those types with cxx types (e.g. UniquePtr), this intermediate
176    /// step is still necessary because bindgen can't otherwise
177    /// give us the templated types (e.g. when faced with the STL
178    /// unique_ptr, bindgen would normally give us std_unique_ptr
179    /// as opposed to std_unique_ptr<T>.)
180    pub(crate) fn get_prelude(&self) -> String {
181        itertools::join(
182            self.by_rs_name
183                .values()
184                .filter_map(|t| t.get_prelude_entry()),
185            "",
186        )
187    }
188
189    /// Returns all known types.
190    pub(crate) fn all_names(&self) -> impl Iterator<Item = &QualifiedName> {
191        self.canonical_names.keys().chain(self.by_rs_name.keys())
192    }
193
194    /// Types which are known to be safe (or unsafe) to hold and pass by
195    /// value in Rust.
196    pub(crate) fn get_pod_safe_types(&self) -> impl Iterator<Item = (QualifiedName, bool)> {
197        let pod_safety = self
198            .all_names()
199            .map(|tn| {
200                (
201                    tn.clone(),
202                    match self.get(tn).unwrap().behavior {
203                        Behavior::CxxContainerPtr
204                        | Behavior::RustStr
205                        | Behavior::RustString
206                        | Behavior::RustByValue
207                        | Behavior::CByValueVecSafe
208                        | Behavior::CByValue
209                        | Behavior::CVariableLengthByValue
210                        | Behavior::CChar16
211                        | Behavior::RustContainerByValueSafe => true,
212                        Behavior::CxxString | Behavior::CxxContainerVector | Behavior::CVoid => {
213                            false
214                        }
215                    },
216                )
217            })
218            .collect::<HashMap<_, _>>();
219        pod_safety.into_iter()
220    }
221
222    pub(crate) fn get_constructor_details(
223        &self,
224        qn: &QualifiedName,
225    ) -> Option<KnownTypeConstructorDetails> {
226        self.get(qn).map(|x| KnownTypeConstructorDetails {
227            has_move_constructor: x.has_move_constructor,
228            has_const_copy_constructor: x.has_const_copy_constructor,
229        })
230    }
231
232    /// Whether this TypePath should be treated as a value in C++
233    /// but a reference in Rust. This only applies to rust::Str
234    /// (C++ name) which is &str in Rust.
235    pub(crate) fn should_dereference_in_cpp(&self, tn: &QualifiedName) -> bool {
236        self.get(tn)
237            .map(|td| matches!(td.behavior, Behavior::RustStr))
238            .unwrap_or(false)
239    }
240
241    /// Whether this can only be passed around using `std::move`
242    pub(crate) fn lacks_copy_constructor(&self, tn: &QualifiedName) -> bool {
243        self.get(tn)
244            .map(|td| {
245                matches!(
246                    td.behavior,
247                    Behavior::CxxContainerPtr
248                        | Behavior::CxxContainerVector
249                        | Behavior::RustContainerByValueSafe
250                )
251            })
252            .unwrap_or(false)
253    }
254
255    /// Here we substitute any names which we know are Special from
256    /// our type database, e.g. std::unique_ptr -> UniquePtr.
257    /// We strip off and ignore
258    /// any PathArguments within this TypePath - callers should
259    /// put them back again if needs be.
260    pub(crate) fn consider_substitution(&self, tn: &QualifiedName) -> Option<TypePath> {
261        self.get(tn).map(|td| td.to_type_path())
262    }
263
264    pub(crate) fn special_cpp_name(&self, rs: &QualifiedName) -> Option<String> {
265        self.get(rs).map(|x| x.cpp_name.to_string())
266    }
267
268    pub(crate) fn is_known_type(&self, ty: &QualifiedName) -> bool {
269        self.get(ty).is_some()
270    }
271
272    /// Whether this is the substitute type we made for some known type.
273    pub(crate) fn is_known_subtitute_type(&self, ty: &QualifiedName) -> bool {
274        if ty.get_namespace().is_empty() {
275            self.all_names()
276                .any(|n| n.get_final_item() == ty.get_final_item())
277        } else {
278            false
279        }
280    }
281
282    pub(crate) fn known_type_type_path(&self, ty: &QualifiedName) -> Option<TypePath> {
283        self.get(ty).map(|td| td.to_type_path())
284    }
285
286    /// Whether this is one of the ctypes (mostly variable length integers)
287    /// which we need to wrap.
288    pub(crate) fn is_ctype(&self, ty: &QualifiedName) -> bool {
289        self.get(ty)
290            .map(|td| {
291                matches!(
292                    td.behavior,
293                    Behavior::CVariableLengthByValue | Behavior::CVoid | Behavior::CChar16
294                )
295            })
296            .unwrap_or(false)
297    }
298
299    /// Whether this is a generic type acceptable to cxx. Otherwise,
300    /// if we encounter a generic, we'll replace it with a synthesized concrete
301    /// type.
302    pub(crate) fn cxx_generic_behavior(&self, ty: &QualifiedName) -> CxxGenericType {
303        self.get(ty)
304            .map(|x| x.get_generic_behavior())
305            .unwrap_or(CxxGenericType::Not)
306    }
307
308    pub(crate) fn is_cxx_acceptable_receiver(&self, ty: &QualifiedName) -> bool {
309        self.get(ty).is_none() // at present, none of our known types can have
310                               // methods attached.
311    }
312
313    pub(crate) fn permissible_within_vector(&self, ty: &QualifiedName) -> bool {
314        self.get(ty)
315            .map(|x| matches!(x.behavior, Behavior::CxxString | Behavior::CByValueVecSafe))
316            .unwrap_or(true)
317    }
318
319    pub(crate) fn permissible_within_unique_ptr(&self, ty: &QualifiedName) -> bool {
320        self.get(ty)
321            .map(|x| {
322                matches!(
323                    x.behavior,
324                    Behavior::CxxString | Behavior::CxxContainerVector
325                )
326            })
327            .unwrap_or(true)
328    }
329
330    pub(crate) fn conflicts_with_built_in_type(&self, ty: &QualifiedName) -> bool {
331        self.get(ty).is_some()
332    }
333
334    pub(crate) fn convertible_from_strs(&self, ty: &QualifiedName) -> bool {
335        self.get(ty)
336            .map(|x| matches!(x.behavior, Behavior::CxxString))
337            .unwrap_or(false)
338    }
339
340    fn insert(&mut self, td: TypeDetails) {
341        let rs_name = td.to_typename();
342        if let Some(extra_non_canonical_name) = &td.extra_non_canonical_name {
343            self.canonical_names.insert(
344                QualifiedName::new_from_cpp_name(extra_non_canonical_name),
345                rs_name.clone(),
346            );
347        }
348        self.canonical_names.insert(
349            QualifiedName::new_from_cpp_name(&td.cpp_name),
350            rs_name.clone(),
351        );
352        self.by_rs_name.insert(rs_name, td);
353    }
354
355    pub(crate) fn get_moveit_safe_types(&self) -> impl Iterator<Item = QualifiedName> + '_ {
356        self.all_names()
357            .filter(|tn| {
358                !matches!(
359                    self.get(tn).unwrap().behavior,
360                    Behavior::CxxString | Behavior::CxxContainerVector
361                )
362            })
363            .cloned()
364    }
365}
366
367fn create_type_database() -> TypeDatabase {
368    let mut db = TypeDatabase::default();
369    db.insert(TypeDetails::new(
370        "cxx::UniquePtr",
371        "std::unique_ptr",
372        Behavior::CxxContainerPtr,
373        None,
374        false,
375        true,
376    ));
377    db.insert(TypeDetails::new(
378        "cxx::CxxVector",
379        "std::vector",
380        Behavior::CxxContainerVector,
381        None,
382        false,
383        true,
384    ));
385    db.insert(TypeDetails::new(
386        "cxx::SharedPtr",
387        "std::shared_ptr",
388        Behavior::CxxContainerPtr,
389        None,
390        true,
391        true,
392    ));
393    db.insert(TypeDetails::new(
394        "cxx::WeakPtr",
395        "std::weak_ptr",
396        Behavior::CxxContainerPtr,
397        None,
398        true,
399        true,
400    ));
401    db.insert(TypeDetails::new(
402        "cxx::CxxString",
403        "std::string",
404        Behavior::CxxString,
405        None,
406        true,
407        true,
408    ));
409    db.insert(TypeDetails::new(
410        "str",
411        "rust::Str",
412        Behavior::RustStr,
413        None,
414        true,
415        false,
416    ));
417    db.insert(TypeDetails::new(
418        "String",
419        "rust::String",
420        Behavior::RustString,
421        None,
422        true,
423        true,
424    ));
425    db.insert(TypeDetails::new(
426        "std::boxed::Box",
427        "rust::Box",
428        Behavior::RustContainerByValueSafe,
429        None,
430        false,
431        true,
432    ));
433    db.insert(TypeDetails::new(
434        "i8",
435        "int8_t",
436        Behavior::CByValueVecSafe,
437        Some("std::os::raw::c_schar".into()),
438        true,
439        true,
440    ));
441    db.insert(TypeDetails::new(
442        "u8",
443        "uint8_t",
444        Behavior::CByValueVecSafe,
445        Some("std::os::raw::c_uchar".into()),
446        true,
447        true,
448    ));
449    for (cpp_type, rust_type) in (4..7).map(|x| 2i32.pow(x)).flat_map(|x| {
450        vec![
451            (format!("uint{x}_t"), format!("u{x}")),
452            (format!("int{x}_t"), format!("i{x}")),
453        ]
454    }) {
455        db.insert(TypeDetails::new(
456            rust_type,
457            cpp_type,
458            Behavior::CByValueVecSafe,
459            None,
460            true,
461            true,
462        ));
463    }
464    db.insert(TypeDetails::new(
465        "bool",
466        "bool",
467        Behavior::CByValue,
468        None,
469        true,
470        true,
471    ));
472
473    db.insert(TypeDetails::new(
474        "core::pin::Pin",
475        "Pin",
476        Behavior::RustByValue, // because this is actually Pin<&something>
477        Some("std::pin::Pin".to_string()),
478        true,
479        false,
480    ));
481
482    let mut insert_ctype = |cname: &str| {
483        let concatenated_name = cname.replace(' ', "");
484        db.insert(TypeDetails::new(
485            format!("autocxx::c_{concatenated_name}"),
486            cname,
487            Behavior::CVariableLengthByValue,
488            Some(format!("std::os::raw::c_{concatenated_name}")),
489            true,
490            true,
491        ));
492        db.insert(TypeDetails::new(
493            format!("autocxx::c_u{concatenated_name}"),
494            format!("unsigned {cname}"),
495            Behavior::CVariableLengthByValue,
496            Some(format!("std::os::raw::c_u{concatenated_name}")),
497            true,
498            true,
499        ));
500    };
501
502    insert_ctype("long");
503    insert_ctype("int");
504    insert_ctype("short");
505    insert_ctype("long long");
506
507    db.insert(TypeDetails::new(
508        "f32",
509        "float",
510        Behavior::CByValueVecSafe,
511        None,
512        true,
513        true,
514    ));
515    db.insert(TypeDetails::new(
516        "f64",
517        "double",
518        Behavior::CByValueVecSafe,
519        None,
520        true,
521        true,
522    ));
523    db.insert(TypeDetails::new(
524        "::std::os::raw::c_char",
525        "char",
526        Behavior::CByValue,
527        None,
528        true,
529        true,
530    ));
531    db.insert(TypeDetails::new(
532        "usize",
533        "size_t",
534        Behavior::CByValueVecSafe,
535        None,
536        true,
537        true,
538    ));
539    db.insert(TypeDetails::new(
540        "autocxx::c_void",
541        "void",
542        Behavior::CVoid,
543        Some("std::os::raw::c_void".into()),
544        false,
545        false,
546    ));
547    db.insert(TypeDetails::new(
548        "autocxx::c_char16_t",
549        "char16_t",
550        Behavior::CChar16,
551        Some("c_char16_t".into()),
552        false,
553        false,
554    ));
555    db
556}