Skip to main content

ritual/
cpp_type.rs

1//! Types for handling information about C++ types.
2
3use crate::cpp_data::CppPath;
4use crate::cpp_ffi_data::{CppFfiType, CppTypeConversionToFfi};
5use ritual_common::errors::{bail, Result, ResultExt};
6use serde_derive::{Deserialize, Serialize};
7use std::hash::Hash;
8use std::hash::Hasher;
9
10#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
11pub enum CppPointerLikeTypeKind {
12    Pointer,
13    Reference,
14    RValueReference,
15}
16
17/// Available built-in C++ numeric types.
18/// All these types have corresponding
19/// `clang::TypeKind` values (except for `CharS` and `CharU`
20/// which map to `CppBuiltInNumericType::Char`)
21#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
22pub enum CppBuiltInNumericType {
23    Bool,
24    Char,
25    SChar,
26    UChar,
27    WChar,
28    Char16,
29    Char32,
30    Short,
31    UShort,
32    Int,
33    UInt,
34    Long,
35    ULong,
36    LongLong,
37    ULongLong,
38    Int128,
39    UInt128,
40    Float,
41    Double,
42    LongDouble,
43}
44
45/// Information about a fixed-size primitive type
46#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
47#[allow(dead_code)]
48pub enum CppSpecificNumericTypeKind {
49    Integer { is_signed: bool },
50    FloatingPoint,
51}
52
53/// Information about a C++ function pointer type
54#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
55pub struct CppFunctionPointerType {
56    /// Return type of the function
57    pub return_type: Box<CppType>,
58    /// Arguments of the function
59    pub arguments: Vec<CppType>,
60    /// Whether arguments are terminated with "..."
61    pub allows_variadic_arguments: bool,
62}
63
64/// Information about a numeric C++ type that is
65/// guaranteed to be the same on all platforms,
66/// e.g. `uint32_t`.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct CppSpecificNumericType {
69    /// Type identifier (most likely a typedef name)
70    pub path: CppPath,
71    /// Size of type in bits
72    pub bits: usize,
73    /// Information about the type (float or integer,
74    /// signed or unsigned)
75    pub kind: CppSpecificNumericTypeKind,
76}
77
78/// Base C++ type. `CppType` can add indirection
79/// and constness to `CppTypeBase`, but otherwise
80/// this enum lists all supported types.
81#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
82pub enum CppType {
83    /// Void
84    Void,
85    /// Built-in C++ primitive type, like int
86    BuiltInNumeric(CppBuiltInNumericType),
87    /// Fixed-size primitive type, like qint64 or int64_t
88    /// (may be translated to Rust's i64)
89    SpecificNumeric(CppSpecificNumericType),
90    /// Pointer sized integer, like qintptr
91    /// (may be translated to Rust's isize)
92    PointerSizedInteger { path: CppPath, is_signed: bool },
93    /// Enum type
94    Enum {
95        /// Name, including namespaces and nested classes
96        path: CppPath,
97    },
98    /// Class type
99    Class(CppPath),
100    /// Template parameter, like `"T"` anywhere inside
101    /// `QVector<T>` declaration
102    TemplateParameter {
103        /// Template instantiation level. For example,
104        /// if there is a template class and a template method in it,
105        /// the class's template parameters will have level = 0 and
106        /// the method's template parameters will have level = 1.
107        /// If only the class or only the method is a template,
108        /// the level will be 0.
109        nested_level: usize,
110        /// Index of the parameter. In `QHash<K, V>` `"K"` has `index = 0`
111        /// and `"V"` has `index = 1`.
112        index: usize,
113
114        /// Declared name of this template parameter
115        name: String,
116    },
117    /// Function pointer type
118    FunctionPointer(CppFunctionPointerType),
119    PointerLike {
120        kind: CppPointerLikeTypeKind,
121        is_const: bool,
122        target: Box<CppType>,
123    },
124}
125
126impl CppBuiltInNumericType {
127    /// Returns C++ code representing this type.
128    pub fn to_cpp_code(&self) -> &'static str {
129        use self::CppBuiltInNumericType::*;
130        match *self {
131            Bool => "bool",
132            Char => "char",
133            SChar => "signed char",
134            UChar => "unsigned char",
135            WChar => "wchar_t",
136            Char16 => "char16_t",
137            Char32 => "char32_t",
138            Short => "short",
139            UShort => "unsigned short",
140            Int => "int",
141            UInt => "unsigned int",
142            Long => "long",
143            ULong => "unsigned long",
144            LongLong => "long long",
145            ULongLong => "unsigned long long",
146            Int128 => "__int128_t",
147            UInt128 => "__uint128_t",
148            Float => "float",
149            Double => "double",
150            LongDouble => "long double",
151        }
152    }
153
154    /// Returns true if this type is some sort of floating point type.
155    pub fn is_float(&self) -> bool {
156        use self::CppBuiltInNumericType::*;
157        match *self {
158            Float | Double | LongDouble => true,
159            _ => false,
160        }
161    }
162
163    /// Returns true if this type is a signed integer.
164    pub fn is_signed_integer(&self) -> bool {
165        use self::CppBuiltInNumericType::*;
166        match *self {
167            SChar | Short | Int | Long | LongLong | Int128 => true,
168            _ => false,
169        }
170    }
171
172    /// Returns true if this type is an unsigned integer.
173    pub fn is_unsigned_integer(&self) -> bool {
174        use self::CppBuiltInNumericType::*;
175        match *self {
176            UChar | Char16 | Char32 | UShort | UInt | ULong | ULongLong | UInt128 => true,
177            _ => false,
178        }
179    }
180
181    /// Returns true if this type is integer but may be signed or
182    /// unsigned, depending on the platform.
183    pub fn is_integer_with_undefined_signedness(&self) -> bool {
184        use self::CppBuiltInNumericType::*;
185        match *self {
186            Char | WChar => true,
187            _ => false,
188        }
189    }
190
191    /// Returns all supported types.
192    pub fn all() -> &'static [CppBuiltInNumericType] {
193        use self::CppBuiltInNumericType::*;
194        &[
195            Bool, Char, SChar, UChar, WChar, Char16, Char32, Short, UShort, Int, UInt, Long, ULong,
196            LongLong, ULongLong, Int128, UInt128, Float, Double, LongDouble,
197        ]
198    }
199}
200
201impl CppPath {
202    /// Attempts to replace template types at `nested_level1`
203    /// within this type with `template_arguments1`.
204    pub fn instantiate(
205        &self,
206        nested_level1: usize,
207        template_arguments1: &[CppType],
208    ) -> Result<CppPath> {
209        let mut new_path = self.clone();
210        for path_item in &mut new_path.items {
211            if let Some(ref mut template_arguments) = path_item.template_arguments {
212                for arg in template_arguments {
213                    *arg = arg.instantiate(nested_level1, template_arguments1)?;
214                }
215            }
216        }
217        Ok(new_path)
218    }
219}
220
221impl CppType {
222    pub fn new_pointer(is_const: bool, target: CppType) -> Self {
223        CppType::PointerLike {
224            kind: CppPointerLikeTypeKind::Pointer,
225            is_const,
226            target: Box::new(target),
227        }
228    }
229
230    pub fn new_reference(is_const: bool, target: CppType) -> Self {
231        CppType::PointerLike {
232            kind: CppPointerLikeTypeKind::Reference,
233            is_const,
234            target: Box::new(target),
235        }
236    }
237
238    #[allow(dead_code)]
239    /// Returns true if this is `void` type.
240    pub fn is_void(&self) -> bool {
241        match *self {
242            CppType::Void => true,
243            _ => false,
244        }
245    }
246    /// Returns true if this is a class type.
247    pub fn is_class(&self) -> bool {
248        match *self {
249            CppType::Class(..) => true,
250            _ => false,
251        }
252    }
253    /// Returns true if this is a template parameter.
254    pub fn is_template_parameter(&self) -> bool {
255        match *self {
256            CppType::TemplateParameter { .. } => true,
257            _ => false,
258        }
259    }
260    /// Returns true if this is a function pointer.
261    pub fn is_function_pointer(&self) -> bool {
262        match *self {
263            CppType::FunctionPointer(..) => true,
264            _ => false,
265        }
266    }
267    /// Returns true if this is a template parameter or a type that
268    /// contains any template parameters.
269    pub fn is_or_contains_template_parameter(&self) -> bool {
270        match *self {
271            CppType::TemplateParameter { .. } => true,
272            CppType::PointerLike { ref target, .. } => target.is_or_contains_template_parameter(),
273            CppType::FunctionPointer(ref type1) => {
274                type1.return_type.is_or_contains_template_parameter()
275                    || type1
276                        .arguments
277                        .iter()
278                        .any(|arg| arg.is_or_contains_template_parameter())
279            }
280            CppType::Class(ref path) => path.items.iter().any(|item| {
281                if let Some(ref template_arguments) = item.template_arguments {
282                    template_arguments
283                        .iter()
284                        .any(|arg| arg.is_or_contains_template_parameter())
285                } else {
286                    false
287                }
288            }),
289            _ => false,
290        }
291    }
292
293    /// Returns C++ code representing this type.
294    pub fn to_cpp_code(&self, function_pointer_inner_text: Option<&str>) -> Result<String> {
295        if !self.is_function_pointer() && function_pointer_inner_text.is_some() {
296            bail!("unexpected function_pointer_inner_text");
297        }
298        match *self {
299            CppType::Void => Ok("void".to_string()),
300            CppType::BuiltInNumeric(ref t) => Ok(t.to_cpp_code().to_string()),
301            CppType::Enum { ref path }
302            | CppType::SpecificNumeric(CppSpecificNumericType { ref path, .. })
303            | CppType::PointerSizedInteger { ref path, .. } => path.to_cpp_code(),
304            //      CppTypeBase::SpecificNumeric { ref name, .. } => Ok(name.clone()),
305            //      CppTypeBase::PointerSizedInteger { ref name, .. } => Ok(name.clone()),
306            CppType::Class(ref path) => path.to_cpp_code(),
307            CppType::TemplateParameter { .. } => {
308                bail!("template parameters are not allowed in C++ code generator");
309            }
310            CppType::FunctionPointer(CppFunctionPointerType {
311                ref return_type,
312                ref arguments,
313                ref allows_variadic_arguments,
314            }) => {
315                if *allows_variadic_arguments {
316                    bail!("function pointers with variadic arguments are not supported");
317                }
318                let mut arg_texts = Vec::new();
319                for arg in arguments {
320                    arg_texts.push(arg.to_cpp_code(None)?);
321                }
322                if let Some(function_pointer_inner_text) = function_pointer_inner_text {
323                    Ok(format!(
324                        "{} (*{})({})",
325                        return_type.as_ref().to_cpp_code(None)?,
326                        function_pointer_inner_text,
327                        arg_texts.join(", ")
328                    ))
329                } else {
330                    bail!("function_pointer_inner_text argument is missing");
331                }
332            }
333            CppType::PointerLike {
334                ref kind,
335                ref is_const,
336                ref target,
337            } => Ok(format!(
338                "{}{}{}",
339                if *is_const { "const " } else { "" },
340                target.to_cpp_code(function_pointer_inner_text)?,
341                match *kind {
342                    CppPointerLikeTypeKind::Pointer => "*",
343                    CppPointerLikeTypeKind::Reference => "&",
344                    CppPointerLikeTypeKind::RValueReference => "&&",
345                }
346            )),
347        }
348    }
349
350    /// Generates string representation of this type
351    /// for debugging output.
352    pub fn to_cpp_pseudo_code(&self) -> String {
353        match *self {
354            CppType::TemplateParameter { ref name, .. } => {
355                return name.to_string(); // format!("T{}_{}", nested_level, index);
356            }
357            CppType::Class(ref base) => return base.to_cpp_pseudo_code(),
358            CppType::FunctionPointer(..) => {
359                return self
360                    .to_cpp_code(Some(&"FN_PTR".to_string()))
361                    .unwrap_or_else(|_| "[?]".to_string())
362            }
363            _ => {}
364        };
365        self.to_cpp_code(None).unwrap_or_else(|_| "[?]".to_string())
366    }
367}
368
369/// Context of usage for a C++ type
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub enum CppTypeRole {
372    /// This type is used as a function's return type
373    ReturnType,
374    /// This type is not used as a function's return type
375    NotReturnType,
376}
377
378pub fn is_qflags(path: &CppPath) -> bool {
379    path.items.len() == 1 && &path.items[0].name == "QFlags"
380}
381
382impl CppType {
383    fn contains_reference(&self) -> bool {
384        if let CppType::PointerLike {
385            ref kind,
386            ref target,
387            ..
388        } = *self
389        {
390            match *kind {
391                CppPointerLikeTypeKind::Pointer => target.contains_reference(),
392                CppPointerLikeTypeKind::Reference | CppPointerLikeTypeKind::RValueReference => true,
393            }
394        } else {
395            false
396        }
397    }
398
399    /// Converts this C++ type to its adaptation for FFI interface,
400    /// removing all features not supported by C ABI
401    /// (e.g. references and passing objects by value).
402    #[allow(clippy::collapsible_if)]
403    pub fn to_cpp_ffi_type(&self, role: CppTypeRole) -> Result<CppFfiType> {
404        let inner = || -> Result<CppFfiType> {
405            if self.is_or_contains_template_parameter() {
406                bail!("template parameters cannot be expressed in FFI");
407            }
408            match self {
409                CppType::FunctionPointer(CppFunctionPointerType {
410                    ref return_type,
411                    ref arguments,
412                    ref allows_variadic_arguments,
413                }) => {
414                    if *allows_variadic_arguments {
415                        bail!("function pointers with variadic arguments are not supported");
416                    }
417                    let mut all_types: Vec<&CppType> = arguments.iter().collect();
418                    all_types.push(return_type.as_ref());
419                    for arg in all_types {
420                        match *arg {
421                            CppType::FunctionPointer(..) => {
422                                // TODO: also ban pointers to function pointers
423                                bail!(
424                                    "function pointers containing nested function pointers are \
425                                     not supported"
426                                );
427                            }
428                            CppType::Class(..) => {
429                                bail!(
430                                    "Function pointers containing classes by value are not \
431                                     supported"
432                                );
433                            }
434                            _ => {}
435                        }
436                        if arg.contains_reference() {
437                            bail!("Function pointers containing references are not supported");
438                        }
439                    }
440                    return Ok(CppFfiType {
441                        ffi_type: self.clone(),
442                        conversion: CppTypeConversionToFfi::NoChange,
443                        original_type: self.clone(),
444                    });
445                }
446                CppType::Class(ref path) => {
447                    if is_qflags(&path) {
448                        return Ok(CppFfiType {
449                            ffi_type: CppType::BuiltInNumeric(CppBuiltInNumericType::UInt),
450                            conversion: CppTypeConversionToFfi::QFlagsToUInt,
451                            original_type: self.clone(),
452                        });
453                    } else {
454                        return Ok(CppFfiType {
455                            ffi_type: CppType::PointerLike {
456                                is_const: role != CppTypeRole::ReturnType,
457                                kind: CppPointerLikeTypeKind::Pointer,
458                                target: Box::new(self.clone()),
459                            },
460                            conversion: CppTypeConversionToFfi::ValueToPointer,
461                            original_type: self.clone(),
462                        });
463                    }
464                }
465                CppType::PointerLike {
466                    ref kind,
467                    ref is_const,
468                    ref target,
469                } => {
470                    match *kind {
471                        CppPointerLikeTypeKind::Pointer => {}
472                        CppPointerLikeTypeKind::Reference => {
473                            if *is_const {
474                                if let CppType::Class(ref path) = **target {
475                                    if is_qflags(path) {
476                                        return Ok(CppFfiType {
477                                            ffi_type: CppType::BuiltInNumeric(
478                                                CppBuiltInNumericType::UInt,
479                                            ),
480                                            // TODO: use a separate conversion type (QFlagsConstRefToUInt)?
481                                            conversion: CppTypeConversionToFfi::QFlagsToUInt,
482                                            original_type: self.clone(),
483                                        });
484                                    }
485                                }
486                            }
487                            return Ok(CppFfiType {
488                                ffi_type: CppType::PointerLike {
489                                    is_const: *is_const,
490                                    kind: CppPointerLikeTypeKind::Pointer,
491                                    target: target.clone(),
492                                },
493                                conversion: CppTypeConversionToFfi::ReferenceToPointer,
494                                original_type: self.clone(),
495                            });
496                        }
497                        CppPointerLikeTypeKind::RValueReference => {
498                            bail!("rvalue references are not supported");
499                        }
500                    }
501                }
502                _ => {}
503            }
504            Ok(CppFfiType {
505                ffi_type: self.clone(),
506                conversion: CppTypeConversionToFfi::NoChange,
507                original_type: self.clone(),
508            })
509        };
510        Ok(inner().with_context(|_| format!("Can't express type to FFI: {:?}", self))?)
511    }
512
513    /// Attempts to replace template types at `nested_level1`
514    /// within this type with `template_arguments1`.
515    #[allow(clippy::if_not_else)]
516    pub fn instantiate(
517        &self,
518        nested_level1: usize,
519        template_arguments1: &[CppType],
520    ) -> Result<CppType> {
521        match self {
522            CppType::TemplateParameter {
523                nested_level,
524                index,
525                ..
526            } => {
527                if *nested_level == nested_level1 {
528                    if *index >= template_arguments1.len() {
529                        bail!("not enough template arguments");
530                    }
531                    Ok(template_arguments1[*index].clone())
532                } else {
533                    Ok(self.clone())
534                }
535            }
536            CppType::Class(ref type1) => Ok(CppType::Class(
537                type1.instantiate(nested_level1, template_arguments1)?,
538            )),
539            CppType::PointerLike {
540                ref kind,
541                ref is_const,
542                ref target,
543            } => Ok(CppType::PointerLike {
544                kind: kind.clone(),
545                is_const: *is_const,
546                target: Box::new(target.instantiate(nested_level1, template_arguments1)?),
547            }),
548            _ => Ok(self.clone()),
549        }
550    }
551}
552
553impl PartialEq for CppSpecificNumericType {
554    fn eq(&self, other: &CppSpecificNumericType) -> bool {
555        // name field is ignored
556        self.bits == other.bits && self.kind == other.kind
557    }
558}
559impl Eq for CppSpecificNumericType {}
560impl Hash for CppSpecificNumericType {
561    fn hash<H: Hasher>(&self, state: &mut H) {
562        self.bits.hash(state);
563        self.kind.hash(state);
564    }
565}