diplomat_tool/
lib.rs

1// Enable once https://github.com/rust-lang/rust/issues/89554 is stable
2// #![deny(non_exhaustive_omitted_patterns)] // diplomat_core uses non_exhaustive a lot; we should never miss its patterns
3
4pub mod config;
5// Custom askama filters
6pub(crate) mod filters;
7
8// Backends
9pub mod c;
10mod cpp;
11mod dart;
12mod demo_gen;
13mod js;
14mod kotlin;
15mod nanobind;
16
17use colored::*;
18use config::toml_value_from_str;
19use config::{find_top_level_attr, Config};
20use core::mem;
21use core::panic;
22use diplomat_core::hir;
23use std::borrow::Cow;
24use std::cell::RefCell;
25use std::collections::HashMap;
26use std::fmt;
27use std::path::Path;
28
29pub use hir::DocsUrlGenerator;
30
31pub fn gen(
32    entry: &Path,
33    target_language: &str,
34    out_folder: &Path,
35    docs_url_gen: &DocsUrlGenerator,
36    mut config: Config,
37    silent: bool,
38) -> std::io::Result<()> {
39    if !entry.exists() {
40        eprintln!(
41            "{}{}\n{}",
42            "Error: ".red().bold(),
43            if entry.file_name().map(|e| e == "lib.rs").unwrap_or_default() {
44                "Could not find the lib.rs file to process."
45            } else {
46                "The entry file does not exist."
47            },
48            format!("{}", std::env::current_dir().unwrap().join(entry).display()).red()
49        );
50        std::process::exit(1);
51    }
52
53    // The HIR backends used to be named "c2", "js2", etc
54    let target_language = target_language.strip_suffix('2').unwrap_or(target_language);
55    let mut attr_validator = hir::BasicAttributeValidator::new(target_language);
56    attr_validator.support = match target_language {
57        "c" => c::attr_support(),
58        "cpp" => cpp::attr_support(),
59        "dart" => dart::attr_support(),
60        "js" => js::attr_support(),
61        "demo_gen" => {
62            // So renames and disables are carried across.
63            attr_validator.other_backend_names = vec!["js".to_string()];
64            demo_gen::attr_support()
65        }
66        "kotlin" => kotlin::attr_support(),
67        "py-nanobind" | "nanobind" => nanobind::attr_support(),
68        o => panic!("Unknown target: {}", o),
69    };
70
71    let module = syn_inline_mod::parse_and_inline_modules(entry);
72
73    // Config:
74    // Just search the top-level lib.rs for the Config attributes for now. We can re-configure this to use AST to search ALL modules if need be.
75    let cfg = find_top_level_attr(module.items.clone());
76    for attr in cfg {
77        for kvp in attr.key_value_pairs {
78            config.set(&kvp.key, toml_value_from_str(&kvp.value));
79        }
80    }
81
82    let config = config.get_overridden(target_language);
83
84    let lowering_config = config.shared_config.lowering_config();
85
86    let tcx =
87        hir::TypeContext::from_syn(&module, lowering_config, attr_validator).unwrap_or_else(|e| {
88            for (ctx, err) in e {
89                eprintln!("Lowering error in {ctx}: {err}");
90            }
91            std::process::exit(1);
92        });
93
94    let (files, errors) = match target_language {
95        "c" => c::run(&tcx, &config, docs_url_gen),
96        "cpp" => cpp::run(&tcx, &config, docs_url_gen),
97        "dart" => dart::run(&tcx, docs_url_gen),
98        "js" => js::run(&tcx, config, docs_url_gen),
99        "py-nanobind" | "nanobind" => nanobind::run(&tcx, config, docs_url_gen),
100        "demo_gen" => {
101            // If we don't already have an import path set up, generate our own imports:
102            if !(config.demo_gen_config.module_name.is_some()
103                || config.demo_gen_config.relative_js_path.is_some())
104            {
105                gen(
106                    entry,
107                    "js",
108                    &out_folder.join("js"),
109                    docs_url_gen,
110                    config.clone(),
111                    silent,
112                )?;
113            }
114            demo_gen::run(entry, &tcx, docs_url_gen, config.clone())
115        }
116        "kotlin" => kotlin::run(&tcx, config.clone(), docs_url_gen),
117        o => panic!("Unknown target: {}", o),
118    };
119
120    let errors = errors.take_all();
121    if !errors.is_empty() {
122        eprintln!("Found errors whilst generating {target_language}:");
123        for error in errors {
124            eprintln!("\t{}: {}", error.0, error.1);
125        }
126        eprintln!("Not generating files due to errors");
127        // Eventually this should use eyre or something
128        std::process::exit(1);
129    }
130
131    if !silent {
132        println!(
133            "{}",
134            format!("Generating {target_language} bindings:")
135                .green()
136                .bold()
137        );
138    }
139    for (subpath, text) in files.take_files() {
140        let out_path = out_folder.join(subpath);
141        if !silent {
142            println!("{}", format!("  {}", out_path.display()).dimmed());
143        }
144        std::fs::create_dir_all(out_path.parent().unwrap()).unwrap();
145        std::fs::write(&out_path, text)?;
146    }
147
148    Ok(())
149}
150
151/// This type abstracts over files being written to.
152#[derive(Default, Debug)]
153pub struct FileMap {
154    // The context types exist as a way to avoid passing around a billion different
155    // parameters. However, passing them around as &mut self restricts the amount of
156    // borrowing that can be done. We instead use a RefCell to guard the specifically mutable bits.
157    files: RefCell<HashMap<String, String>>,
158}
159
160impl FileMap {
161    pub fn take_files(self) -> HashMap<String, String> {
162        mem::take(&mut *self.files.borrow_mut())
163    }
164
165    pub fn add_file(&self, name: String, contents: String) {
166        if self.files.borrow().get(&name).is_some() {
167            panic!("File map already contains {}", name)
168        }
169        self.files.borrow_mut().insert(name, contents);
170    }
171}
172
173/// This type acts as a "store" for errors, which can be appended to.
174/// Keeps track of the context in which an error was generated.
175///
176/// You can use [`set_context_ty()`] and [`set_context_method()`] to set the context
177/// as a type or method. They will return scope guards that will automatically pop the stack
178/// once they go out of scope, so you don't have to worry about errors originating from code
179/// that does not set a context.
180#[derive(Default)]
181pub struct ErrorStore<'tcx, E> {
182    /// The stack of contexts reached so far
183    context: RefCell<ErrorContext<'tcx>>,
184    errors: RefCell<Vec<(ErrorContext<'tcx>, E)>>,
185}
186
187impl<'tcx, E> ErrorStore<'tcx, E> {
188    /// Set the context to a named type. Will return a scope guard that will automatically
189    /// clear the context on drop.
190    pub fn set_context_ty<'a>(&'a self, ty: Cow<'tcx, str>) -> ErrorContextGuard<'a, 'tcx, E> {
191        let new = ErrorContext { ty, method: None };
192        let old = mem::replace(&mut *self.context.borrow_mut(), new);
193        ErrorContextGuard(self, old)
194    }
195
196    /// Set the context to a named method. Will return a scope guard that will automatically
197    /// clear the context on drop.
198    pub fn set_context_method<'a>(
199        &'a self,
200        method: Cow<'tcx, str>,
201    ) -> ErrorContextGuard<'a, 'tcx, E> {
202        let new = ErrorContext {
203            ty: self.context.borrow().ty.clone(),
204            method: Some(method),
205        };
206
207        let old = mem::replace(&mut *self.context.borrow_mut(), new);
208        ErrorContextGuard(self, old)
209    }
210
211    pub fn push_error(&self, error: E) {
212        self.errors
213            .borrow_mut()
214            .push((self.context.borrow().clone(), error));
215    }
216
217    pub fn take_all(&self) -> Vec<(impl fmt::Display + 'tcx, E)> {
218        mem::take(&mut self.errors.borrow_mut())
219    }
220}
221
222/// The context in which an error was discovered
223#[derive(Default, Clone)]
224struct ErrorContext<'tcx> {
225    ty: Cow<'tcx, str>,
226    method: Option<Cow<'tcx, str>>,
227}
228
229impl fmt::Display for ErrorContext<'_> {
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        let ty = &self.ty;
232        if let Some(ref method) = self.method {
233            write!(f, "{ty}::{method}")
234        } else {
235            ty.fmt(f)
236        }
237    }
238}
239
240/// Scope guard terminating the context created `set_context_*` method on [`ErrorStore`]
241#[must_use]
242pub struct ErrorContextGuard<'a, 'tcx, E>(&'a ErrorStore<'tcx, E>, ErrorContext<'tcx>);
243
244impl<E> Drop for ErrorContextGuard<'_, '_, E> {
245    fn drop(&mut self) {
246        let _ = mem::replace(&mut *self.0.context.borrow_mut(), mem::take(&mut self.1));
247    }
248}