interoptopus/inventory/
core.rs

1use crate::Error;
2use crate::backend::{IndentWriter, extract_namespaces_from_types, holds_opaque_without_ref, types_from_functions_types};
3use crate::lang::{Constant, Function, Type};
4use crate::pattern::LibraryPattern;
5use std::collections::HashSet;
6use std::fs::File;
7use std::path::Path;
8
9const FORBIDDEN_NAMES: [&str; 139] = [
10    "abstract",
11    "add",
12    "alias",
13    "allows",
14    "and",
15    "args",
16    "as",
17    "ascending",
18    "assert",
19    "async",
20    "await",
21    "base",
22    "bool",
23    "break",
24    "by",
25    "byte",
26    "case",
27    "catch",
28    "char",
29    "checked",
30    "class",
31    "const",
32    "continue",
33    "decimal",
34    "def",
35    "default",
36    "del",
37    "delegate",
38    "descending",
39    "do",
40    "double",
41    "dynamic",
42    "elif",
43    "else",
44    "enum",
45    "equals",
46    "event",
47    "except",
48    "explicit",
49    "extension",
50    "extern",
51    "false",
52    "False",
53    "field",
54    "file",
55    "finally",
56    "fixed",
57    "float",
58    "for",
59    "foreach",
60    "from",
61    "get",
62    "global",
63    "goto",
64    "group",
65    "if",
66    "implicit",
67    "import",
68    "in",
69    "init",
70    "int",
71    "interface",
72    "internal",
73    "into",
74    "is",
75    "join",
76    "lambda",
77    "let",
78    "lock",
79    "long",
80    "managed",
81    "nameof",
82    "namespace",
83    "new",
84    "nint",
85    "None",
86    "nonlocal",
87    "not",
88    "notnull",
89    "nuint",
90    "null",
91    "object",
92    "on",
93    "operator",
94    "or",
95    "orderby",
96    "out",
97    "override",
98    "params",
99    "partial",
100    "pass",
101    "private",
102    "protected",
103    "public",
104    "raise",
105    "readonly",
106    "record",
107    "ref",
108    "remove",
109    "required",
110    "return",
111    "sbyte",
112    "scoped",
113    "sealed",
114    "select",
115    "set",
116    "short",
117    "signed",
118    "sizeof",
119    "stackalloc",
120    "static",
121    "string",
122    "struct",
123    "switch",
124    "this",
125    "throw",
126    "true",
127    "True",
128    "try",
129    "typedef",
130    "typeof",
131    "uint",
132    "ulong",
133    "unchecked",
134    "unmanaged",
135    "unsafe",
136    "unsigned",
137    "ushort",
138    "using",
139    "value",
140    "var",
141    "virtual",
142    "void",
143    "volatile",
144    "when",
145    "where",
146    "while",
147    "with",
148    "yield",
149];
150
151/// Tells the [`InventoryBuilder`] what to register.
152///
153/// Most users won't need to touch this enum directly, as its variants are usually created via the [`function`](crate::function), [`constant`](crate::constant), [`extra_type`](crate::extra_type) and [`pattern`](crate::pattern!) macros.
154#[derive(Debug)]
155pub enum Symbol {
156    Function(Function),
157    Constant(Constant),
158    Type(Type),
159    Pattern(LibraryPattern),
160}
161
162/// Produces a [`Inventory`] inside your inventory function, **start here**.🔥
163///
164/// # Example
165///
166/// Define an inventory function containing a function, constant, and an extra type.
167/// This function can be called from your unit tests and the returned [`Inventory`] used to create bindings.
168///
169/// ```rust
170/// use interoptopus::{function, constant, extra_type, pattern, ffi_function, ffi_constant, ffi_type};
171/// use interoptopus::inventory::{Inventory, InventoryBuilder};
172///
173/// // First, define some items our DLL uses or needs.
174///
175/// #[ffi_function]
176/// pub fn primitive_void() { }
177///
178/// #[ffi_constant]
179/// pub const MY_CONSTANT: u32 = 123;
180///
181/// #[ffi_type]
182/// pub struct ExtraType<T> {
183///     x: T
184/// }
185///
186/// // Then list all items for which to generate bindings. Call this function
187/// // from another crate or unit test and feed the `Library` into a backend to
188/// // generate bindings for a specific language.
189/// pub fn my_inventory() -> Inventory {
190///     Inventory::builder()
191///         .register(function!(primitive_void))
192///         .register(constant!(MY_CONSTANT))
193///         .register(extra_type!(ExtraType<f32>))
194///         .validate()
195///         .build()
196/// }
197/// ```
198#[derive(Default, Debug)]
199pub struct InventoryBuilder {
200    functions: Vec<Function>,
201    ctypes: Vec<Type>,
202    constants: Vec<Constant>,
203    patterns: Vec<LibraryPattern>,
204    allow_reserved_names: bool,
205}
206
207impl InventoryBuilder {
208    /// Start creating a new library.
209    #[must_use]
210    const fn new() -> Self {
211        Self { functions: Vec::new(), ctypes: Vec::new(), constants: Vec::new(), patterns: Vec::new(), allow_reserved_names: false }
212    }
213
214    /// Registers a symbol.
215    ///
216    /// Call this with the result of a [`function`](crate::function), [`constant`](crate::constant), [`extra_type`](crate::extra_type) or [`pattern`](crate::pattern!) macro,
217    /// see the example above.
218    #[must_use]
219    pub fn register(mut self, s: Symbol) -> Self {
220        match s {
221            Symbol::Function(x) => self.functions.push(x),
222            Symbol::Constant(x) => self.constants.push(x),
223            Symbol::Type(x) => self.ctypes.push(x),
224            Symbol::Pattern(x) => {
225                match &x {
226                    LibraryPattern::Service(x) => {
227                        self.functions.push(x.destructor().clone());
228                        self.functions.extend(x.constructors().iter().cloned());
229                        self.functions.extend(x.methods().iter().cloned());
230                    }
231                    LibraryPattern::Builtins(x) => {
232                        self.functions.extend(x.functions().iter().cloned());
233                    }
234                }
235                self.patterns.push(x);
236            }
237        }
238
239        self
240    }
241
242    /// Does additional sanity checking, highly recommended.
243    ///
244    /// This method tries to detect FFI issues that are hard to detect otherwise, and would
245    /// cause issues in any backend.
246    ///
247    /// # Panics
248    ///
249    /// If a function, type, or pattern is detected that doesn't make sense in interop
250    /// generation, a panic will be raised.
251    #[must_use]
252    pub fn validate(self) -> Self {
253        // Check for opaque parameters and return values
254        for x in &self.functions {
255            let has_opaque_param = x.signature().params().iter().any(|x| holds_opaque_without_ref(x.the_type()));
256            assert!(!has_opaque_param, "Function `{}` has a (nested) opaque parameter. This can cause UB.", x.name());
257
258            let has_opaque_rval = holds_opaque_without_ref(x.signature().rval());
259            assert!(!has_opaque_rval, "Function `{}` has a (nested) opaque return value. This can cause UB.", x.name());
260        }
261
262        if !self.allow_reserved_names {
263            validate_symbol_names(&self.functions, &self.ctypes);
264        }
265
266        self
267    }
268
269    /// Allows reserved names for inventory items.
270    ///
271    /// When set, you may use reserved names for your functions
272    /// and fields, for example, having a field called `public`.
273    ///
274    /// When not set, [`self.validate`] may panic if it detects
275    /// such items.
276    #[must_use]
277    pub fn allow_reserved_names(mut self) -> Self {
278        self.allow_reserved_names = true;
279        self
280    }
281
282    /// Produce the [`Inventory`].
283    #[must_use]
284    pub fn build(self) -> Inventory {
285        Inventory::new(self.functions, self.constants, self.patterns, self.ctypes.as_slice())
286    }
287}
288
289/// Holds FFI-relevant items, produced via [`InventoryBuilder`], ingested by backends.
290#[derive(Clone, Debug, PartialOrd, PartialEq, Default)]
291pub struct Inventory {
292    functions: Vec<Function>,
293    ctypes: Vec<Type>,
294    constants: Vec<Constant>,
295    patterns: Vec<LibraryPattern>,
296    namespaces: Vec<String>,
297}
298
299/// References to items contained within an [`Inventory`].
300///
301/// This enum is primarily used by functions such as [`Inventory::filter`] that need to handle
302/// items of various types.
303#[derive(Clone, Debug, PartialEq)]
304pub enum InventoryItem<'a> {
305    Function(&'a Function),
306    CType(&'a Type),
307    Constant(&'a Constant),
308    Pattern(&'a LibraryPattern),
309    Namespace(&'a str),
310}
311
312impl Inventory {
313    /// Produce a new inventory for the given functions, constants and patterns.
314    ///
315    /// Type information will be automatically derived from the used fields and parameters.
316    pub(crate) fn new(functions: Vec<Function>, constants: Vec<Constant>, patterns: Vec<LibraryPattern>, extra_types: &[Type]) -> Self {
317        let mut ctypes = types_from_functions_types(&functions, extra_types);
318        let mut namespaces = HashSet::new();
319
320        // Extract namespace information
321        extract_namespaces_from_types(&ctypes, &mut namespaces);
322        namespaces.extend(functions.iter().map(|x| x.meta().module().to_string()));
323        namespaces.extend(constants.iter().map(|x| x.meta().module().to_string()));
324
325        let mut namespaces = namespaces.iter().cloned().collect::<Vec<String>>();
326        namespaces.sort();
327
328        // Dont sort functions
329        // functions.sort();
330
331        ctypes.sort();
332        // constants.sort(); TODO: do sort constants (issue with Ord and float values ...)
333
334        Self { functions, ctypes, constants, patterns, namespaces }
335    }
336
337    /// Returns a new [`InventoryBuilder`], start here.
338    #[must_use]
339    pub const fn builder() -> InventoryBuilder {
340        InventoryBuilder::new()
341    }
342
343    /// Return all functions registered.
344    #[must_use]
345    pub fn functions(&self) -> &[Function] {
346        &self.functions
347    }
348
349    /// Returns all found types; this includes types directly used in fields and parameters, and
350    /// all their recursive constitutents.
351    #[must_use]
352    pub fn ctypes(&self) -> &[Type] {
353        &self.ctypes
354    }
355
356    /// Return all registered constants.
357    #[must_use]
358    pub fn constants(&self) -> &[Constant] {
359        &self.constants
360    }
361
362    /// Return all known namespaces.
363    #[must_use]
364    pub fn namespaces(&self) -> &[String] {
365        &self.namespaces
366    }
367
368    /// Return all registered [`LibraryPattern`]. In contrast, [`TypePattern`](crate::pattern::TypePattern)
369    /// will be found inside the types returned via [`ctypes()`](Self::ctypes).
370    #[must_use]
371    pub fn patterns(&self) -> &[LibraryPattern] {
372        &self.patterns
373    }
374
375    /// Return a new [`Inventory`] filtering items by a predicate.
376    ///
377    /// Useful for removing duplicate symbols when generating bindings split across multiple files.
378    ///
379    /// # Examples
380    ///
381    /// Here we filter an inventory, keeping only types, removing all other items.
382    ///
383    /// ```rust
384    /// # use interoptopus::inventory::{Inventory, InventoryItem};
385    /// #
386    /// # let inventory = Inventory::default();
387    /// #
388    /// let filtered = inventory.filter(|x| {
389    ///     match x {
390    ///         InventoryItem::CType(_) => true,
391    ///         _ => false,
392    ///     }
393    /// });
394    /// ```
395    #[must_use]
396    pub fn filter<P: FnMut(InventoryItem) -> bool>(&self, mut predicate: P) -> Self {
397        let functions: Vec<Function> = self.functions.iter().filter(|x| predicate(InventoryItem::Function(x))).cloned().collect();
398        let ctypes: Vec<Type> = self.ctypes.iter().filter(|x| predicate(InventoryItem::CType(x))).cloned().collect();
399        let constants: Vec<Constant> = self.constants.iter().filter(|x| predicate(InventoryItem::Constant(x))).cloned().collect();
400        let patterns: Vec<LibraryPattern> = self.patterns.iter().filter(|x| predicate(InventoryItem::Pattern(x))).cloned().collect();
401        let namespaces: Vec<String> = self.namespaces.iter().filter(|x| predicate(InventoryItem::Namespace(x))).cloned().collect();
402
403        Self { functions, ctypes, constants, patterns, namespaces }
404    }
405}
406
407/// Main entry point for backends to generate language bindings.
408///
409/// This trait will be implemented by each backend and is the main way to interface with a generator.
410pub trait Bindings {
411    /// Generates FFI binding code and writes them to the [`IndentWriter`].
412    ///
413    /// # Errors
414    /// Can result in an error if I/O failed.
415    fn write_to(&self, w: &mut IndentWriter) -> Result<(), Error>;
416
417    /// Convenience method to write FFI bindings to the specified file with default indentation.
418    ///
419    /// # Errors
420    /// Can result in an error if I/O failed.
421    fn write_file<P: AsRef<Path>>(&self, file_name: P) -> Result<(), Error> {
422        let mut file = File::create(file_name)?;
423        let mut writer = IndentWriter::new(&mut file);
424
425        self.write_to(&mut writer)
426    }
427
428    /// Convenience method to write FFI bindings to a string.
429    ///
430    /// # Errors
431    /// Can result in an error if I/O failed.
432    fn to_string(&self) -> Result<String, Error> {
433        let mut vec = Vec::new();
434        let mut writer = IndentWriter::new(&mut vec);
435        self.write_to(&mut writer)?;
436        Ok(String::from_utf8(vec)?)
437    }
438}
439
440fn validate_symbol_names(functions: &[Function], ctypes: &[Type]) {
441    // Check function names and parameter names.
442    for func in functions {
443        let name = func.name().to_lowercase();
444        assert!(!FORBIDDEN_NAMES.contains(&name.as_str()), "Function `{name}` has a forbidden name that might cause issues in other languages.");
445
446        for param in func.signature().params() {
447            let param_name = param.name().to_lowercase();
448            assert!(
449                !FORBIDDEN_NAMES.contains(&param_name.as_str()),
450                "Parameter `{param_name}` in function `{name}` has a forbidden name that might cause issues in other languages."
451            );
452        }
453    }
454
455    // Check type names and field/variant names.
456    for ctype in ctypes {
457        match ctype {
458            Type::Composite(composite) => {
459                let type_name = composite.rust_name();
460                assert!(!FORBIDDEN_NAMES.contains(&type_name), "Type `{type_name}` has a forbidden name that might cause issues in other languages.");
461                for field in composite.fields() {
462                    let field_name = field.name();
463                    assert!(
464                        !FORBIDDEN_NAMES.contains(&field_name),
465                        "Field `{field_name}` in type `{type_name}` has a forbidden name that might cause issues in other languages."
466                    );
467                }
468            }
469            Type::Enum(enum_type) => {
470                let type_name = enum_type.rust_name();
471                // Inline the format parameter using Rust's string interpolation.
472                assert!(!FORBIDDEN_NAMES.contains(&type_name), "Enum `{type_name}` has a forbidden name that might cause issues in other languages.");
473                for variant in enum_type.variants() {
474                    let variant_name = variant.name();
475                    assert!(
476                        !FORBIDDEN_NAMES.contains(&variant_name),
477                        "Variant `{variant_name}` in enum `{type_name}` has a forbidden name that might cause issues in other languages."
478                    );
479                }
480            }
481            Type::Opaque(opaque) => {
482                let type_name = opaque.rust_name();
483                assert!(!FORBIDDEN_NAMES.contains(&type_name), "Opaque type `{type_name}` has a forbidden name that might cause issues in other languages.");
484            }
485            _ => {}
486        }
487    }
488}