Skip to main content

microcom_codegen/
rust.rs

1//! Rust bindings generator.
2//!
3//! For every IDL item the generated code contains:
4//!
5//! * enum `E` -> `#[repr(transparent)] struct E(pub i32)` with one associated
6//!   constant per member (a newtype rather than a Rust `enum` because the
7//!   native side may hand over flag combinations or values added later);
8//! * struct `S` -> `#[repr(C)] struct S` with snake_case fields;
9//! * interface `IFoo`:
10//!   * `IFooVtbl` — `#[repr(C)]` table of `unsafe extern "system" fn` slots,
11//!     starting with the base interface's table (the calling convention of
12//!     COM: `stdcall` on 32-bit x86 Windows, the C convention everywhere
13//!     else);
14//!   * `IFoo` — `#[repr(C)]` struct holding the vtable pointer; a
15//!     `*mut IFoo` is the native interface pointer, `&IFoo` derefs to the
16//!     base interface and carries the snake_case proxy methods;
17//!   * `IFooImpl` — the trait Rust code implements to provide the interface
18//!     to native code, `IFoo::from_impl(value)` wraps an implementation into
19//!     a COM object and returns the owning `ComPtr<IFoo>`.
20//!
21//! Signature mapping shared by proxies and `Impl` traits:
22//!
23//! | IDL                                   | Rust                               |
24//! |---------------------------------------|------------------------------------|
25//! | `HRESULT M(...)`                      | `-> Result<(), HResult>`           |
26//! | `HRESULT M(..., IFoo** ppv)` (1)      | `-> Result<Option<ComPtr<IFoo>>, HResult>` |
27//! | `HRESULT M(..., T* ret)` (1)          | `-> Result<T, HResult>`            |
28//! | `IFoo* M()`                           | `-> Option<ComPtr<IFoo>>` (owned)  |
29//! | `IFoo* p`                             | `p: Option<&IFoo>` (borrowed)      |
30//! | `char* s` / `[const] char* s`         | `s: Option<&CStr>`                 |
31//! | `[const] T& r`                        | `r: &T`                            |
32//! | other pointers                        | raw pointers (proxy is `unsafe fn`)|
33//! | `bool`                                | `bool` (C++ `bool` in the header)  |
34//!
35//! (1) only for a trailing parameter named `ret`, `retOut`, `ppv` or
36//! `result`, or marked `[out]` or `[retval]`; every other pointer parameter
37//! is passed through unchanged.
38//!
39//! Types the IDL does not declare:
40//!
41//! * `@rust-map NAME type` names the Rust type of `NAME` (the counterpart
42//!   of the `@clr-map` lines of the IDL files of the C# generator):
43//!   `@rust-map HWND isize`, `@rust-map REFIID *const Guid`;
44//! * `@rust-types-from path` imports everything `path` has into the
45//!   generated code, where every other unknown name is looked up (the
46//!   structures and enumerations a file declares beside its IDL):
47//!   `@rust-types-from super`.
48
49use std::collections::{HashMap, HashSet};
50use std::fmt::Write;
51
52use crate::ast::*;
53
54#[derive(Debug, Clone)]
55pub struct RustOptions {
56    /// Path of the runtime crate in the generated code.
57    pub runtime: String,
58}
59
60impl Default for RustOptions {
61    fn default() -> Self {
62        RustOptions { runtime: "::ferroui_microcom".to_string() }
63    }
64}
65
66const OUT_PARAM_NAMES: &[&str] = &["ret", "retOut", "ppv", "result"];
67
68const KEYWORDS: &[&str] = &[
69    "as", "async", "await", "abstract", "become", "box", "break", "const", "continue", "crate",
70    "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if", "impl", "in",
71    "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
72    "return", "self", "Self", "static", "struct", "super", "trait", "true", "try", "type",
73    "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
74];
75
76/// `GetClientSize` -> `get_client_size`, `texImageIOSurface2D` ->
77/// `tex_image_io_surface2d`, `RootProvider_GetWindow` -> `root_provider_get_window`.
78pub fn snake_case(name: &str) -> String {
79    let chars: Vec<char> = name.chars().collect();
80    let mut out = String::new();
81    for (i, &c) in chars.iter().enumerate() {
82        if c.is_ascii_uppercase() {
83            let prev = if i > 0 { Some(chars[i - 1]) } else { None };
84            let next = chars.get(i + 1).copied();
85            let boundary = match prev {
86                Some(p) if p.is_ascii_lowercase() => true,
87                Some(p) if p.is_ascii_uppercase() => next.is_some_and(|n| n.is_ascii_lowercase()),
88                _ => false,
89            };
90            if boundary && !out.ends_with('_') {
91                out.push('_');
92            }
93            out.push(c.to_ascii_lowercase());
94        } else {
95            out.push(c);
96        }
97    }
98    if KEYWORDS.contains(&out.as_str()) || out == "this" {
99        out.push('_');
100    }
101    out
102}
103
104fn primitive(name: &str) -> Option<&'static str> {
105    Some(match name {
106        "bool" => "bool",
107        "int" => "i32",
108        "uint" | "unsigned int" => "u32",
109        "byte" | "unsigned char" => "u8",
110        "char" => "::core::ffi::c_char",
111        "short" => "i16",
112        "ushort" | "unsigned short" => "u16",
113        "float" => "f32",
114        "double" => "f64",
115        "long" => "::core::ffi::c_long",
116        "unsigned long" => "::core::ffi::c_ulong",
117        "size_t" => "usize",
118        "u_int64_t" | "uint64_t" => "u64",
119        "int64_t" => "i64",
120        "int32_t" => "i32",
121        "uint32_t" => "u32",
122        "void" => "::core::ffi::c_void",
123        "HRESULT" => "RawHResult",
124        _ => return None,
125    })
126}
127
128struct Ctx<'a> {
129    interfaces: HashSet<&'a str>,
130    known: HashSet<&'a str>,
131    /// `@rust-map NAME type`.
132    maps: HashMap<&'a str, &'a str>,
133    /// `@rust-types-from path`.
134    types_from: Option<&'a str>,
135}
136
137impl Ctx<'_> {
138    fn is_interface(&self, name: &str) -> bool {
139        self.interfaces.contains(name)
140    }
141
142    fn base_type(&self, name: &str) -> Result<String, String> {
143        if let Some(mapped) = self.maps.get(name) {
144            Ok(mapped.to_string())
145        } else if let Some(p) = primitive(name) {
146            Ok(p.to_string())
147        } else if self.known.contains(name) {
148            Ok(name.to_string())
149        } else if self.types_from.is_some() {
150            // Found through the import of everything the path has (see `generate`).
151            Ok(name.to_string())
152        } else {
153            Err(format!("unknown type '{name}'"))
154        }
155    }
156
157    /// ABI-level type with `levels` pointer indirections.
158    fn raw_type_n(&self, name: &str, levels: u32, is_const: bool) -> Result<String, String> {
159        let mut s = String::new();
160        for i in 0..levels {
161            // `const T*`: constness applies to the pointee of the innermost pointer.
162            s.push_str(if is_const && i + 1 == levels { "*const " } else { "*mut " });
163        }
164        s.push_str(&self.base_type(name)?);
165        Ok(s)
166    }
167
168    fn raw_type(&self, ty: &TypeRef, is_const: bool) -> Result<String, String> {
169        self.raw_type_n(&ty.name, ty.pointers + ty.reference as u32, is_const)
170    }
171}
172
173/// How a parameter appears in proxies / `Impl` traits.
174enum ParamKind {
175    /// Passed through unchanged (values and uninterpreted raw pointers).
176    Plain { raw_pointer: bool },
177    Interface,
178    CStr { is_const: bool },
179    ConstRef,
180}
181
182enum OutKind {
183    Interface(String),
184    Value(String),
185}
186
187struct ParamInfo {
188    name: String,
189    raw_ty: String,
190    hi_ty: String,
191    kind: ParamKind,
192}
193
194enum RetKind {
195    Unit,
196    HResult,
197    Interface(String),
198    Plain(String),
199}
200
201struct MethodInfo<'a> {
202    method: &'a Method,
203    snake: String,
204    params: Vec<ParamInfo>,
205    /// Trailing out-parameter folded into the return value: (name, raw type, kind).
206    out: Option<(String, String, OutKind)>,
207    ret: RetKind,
208    raw_ret: String,
209    hi_ret: String,
210    needs_unsafe: bool,
211}
212
213fn analyze<'a>(ctx: &Ctx, m: &'a Method) -> Result<MethodInfo<'a>, String> {
214    let rt = &m.return_type;
215    if rt.reference {
216        return Err("reference return types are not supported".into());
217    }
218    let ret = if rt.pointers == 0 && rt.name == "void" {
219        RetKind::Unit
220    } else if rt.pointers == 0 && rt.name == "HRESULT" {
221        RetKind::HResult
222    } else if rt.pointers == 1 && ctx.is_interface(&rt.name) {
223        RetKind::Interface(rt.name.clone())
224    } else {
225        RetKind::Plain(ctx.raw_type(rt, false)?)
226    };
227    let raw_ret = match &ret {
228        RetKind::Unit => String::new(),
229        RetKind::HResult => " -> RawHResult".to_string(),
230        RetKind::Interface(n) => format!(" -> *mut {n}"),
231        RetKind::Plain(t) => format!(" -> {t}"),
232    };
233
234    let mut params = Vec::new();
235    let mut out = None;
236    for (idx, p) in m.params.iter().enumerate() {
237        let name = snake_case(&p.name);
238        let is_const = p.is_const();
239        let raw_ty = ctx.raw_type(&p.ty, is_const)?;
240        let ty = &p.ty;
241        let is_last = idx + 1 == m.params.len();
242
243        if is_last
244            && matches!(ret, RetKind::HResult)
245            && (OUT_PARAM_NAMES.contains(&p.name.as_str())
246                || has_attr(&p.attributes, "out")
247                || has_attr(&p.attributes, "retval"))
248            && ty.pointers >= 1
249            && !ty.reference
250            && !is_const
251        {
252            let kind = if ctx.is_interface(&ty.name) {
253                (ty.pointers == 2).then(|| OutKind::Interface(ty.name.clone()))
254            } else if ty.pointers == 1 && (ty.name == "void" || ty.name == "char") {
255                None
256            } else {
257                Some(OutKind::Value(ctx.raw_type_n(&ty.name, ty.pointers - 1, false)?))
258            };
259            if let Some(kind) = kind {
260                out = Some((name, raw_ty, kind));
261                continue;
262            }
263        }
264
265        let (kind, hi_ty) = if ty.reference {
266            if !is_const || ty.pointers != 0 {
267                return Err(format!("{}: only '[const] T&' references are supported", m.name));
268            }
269            (ParamKind::ConstRef, format!("&{}", ctx.base_type(&ty.name)?))
270        } else if ty.pointers == 1 && ctx.is_interface(&ty.name) {
271            (ParamKind::Interface, format!("Option<&{}>", ty.name))
272        } else if ty.pointers == 1 && ty.name == "char" {
273            (ParamKind::CStr { is_const }, "Option<&::core::ffi::CStr>".to_string())
274        } else {
275            // A name that is mapped to a pointer type is a raw pointer too.
276            (ParamKind::Plain { raw_pointer: ty.pointers > 0 || raw_ty.starts_with('*') }, raw_ty.clone())
277        };
278        params.push(ParamInfo { name, raw_ty, hi_ty, kind });
279    }
280
281    let hi_ret = match (&ret, &out) {
282        (RetKind::Unit, _) => String::new(),
283        (RetKind::HResult, None) => " -> Result<(), HResult>".to_string(),
284        (RetKind::HResult, Some((_, _, OutKind::Interface(n)))) => {
285            format!(" -> Result<Option<ComPtr<{n}>>, HResult>")
286        }
287        (RetKind::HResult, Some((_, _, OutKind::Value(t)))) => format!(" -> Result<{t}, HResult>"),
288        (RetKind::Interface(n), _) => format!(" -> Option<ComPtr<{n}>>"),
289        (RetKind::Plain(t), _) => format!(" -> {t}"),
290    };
291    let needs_unsafe =
292        params.iter().any(|p| matches!(p.kind, ParamKind::Plain { raw_pointer: true }));
293
294    Ok(MethodInfo { method: m, snake: snake_case(&m.name), params, out, ret, raw_ret, hi_ret, needs_unsafe })
295}
296
297impl MethodInfo<'_> {
298    fn hi_params(&self) -> String {
299        self.params.iter().map(|p| format!(", {}: {}", p.name, p.hi_ty)).collect()
300    }
301
302    fn raw_params(&self) -> String {
303        let mut s: String = self.params.iter().map(|p| format!(", {}: {}", p.name, p.raw_ty)).collect();
304        if let Some((name, raw, _)) = &self.out {
305            write!(s, ", {name}: {raw}").unwrap();
306        }
307        s
308    }
309
310    fn forward_args(&self) -> String {
311        self.params.iter().map(|p| format!(", {}", p.name)).collect()
312    }
313}
314
315/// Evaluates the small expression language used for enum values
316/// (integer literals, references to earlier members, `|`, `<<`, unary `-`).
317fn eval_enum_expr(expr: &str, known: &HashMap<String, i64>) -> Option<i64> {
318    let mut acc = 0i64;
319    for term in expr.split('|') {
320        let mut parts = term.split("<<");
321        let mut v = eval_atom(parts.next()?.trim(), known)?;
322        for shift in parts {
323            v = v.checked_shl(u32::try_from(eval_atom(shift.trim(), known)?).ok()?)?;
324        }
325        acc |= v;
326    }
327    Some(acc)
328}
329
330fn eval_atom(s: &str, known: &HashMap<String, i64>) -> Option<i64> {
331    let s = s.trim_start_matches('(').trim_end_matches(')').trim();
332    if let Some(rest) = s.strip_prefix('-') {
333        return eval_atom(rest, known).map(|v| -v);
334    }
335    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
336        return i64::from_str_radix(hex, 16).ok();
337    }
338    s.parse::<i64>().ok().or_else(|| known.get(s).copied())
339}
340
341fn parse_uuid(uuid: &str) -> Result<u128, String> {
342    let hex: String = uuid.chars().filter(|c| *c != '-').collect();
343    if hex.len() != 32 {
344        return Err(format!("invalid uuid '{uuid}'"));
345    }
346    u128::from_str_radix(&hex, 16).map_err(|_| format!("invalid uuid '{uuid}'"))
347}
348
349pub fn generate(idl: &Idl, options: &RustOptions) -> Result<String, String> {
350    let mut interfaces: HashSet<&str> = idl.interfaces.iter().map(|i| i.name.as_str()).collect();
351    interfaces.insert("IUnknown");
352    let mut known = interfaces.clone();
353    known.extend(idl.enums.iter().map(|e| e.name.as_str()));
354    known.extend(idl.structs.iter().map(|s| s.name.as_str()));
355    let mut maps = HashMap::new();
356    for map in idl.directive_values("rust-map") {
357        let (name, ty) = map
358            .split_once(char::is_whitespace)
359            .ok_or_else(|| format!("@rust-map {map}: expected a name and a type"))?;
360        maps.insert(name, ty.trim());
361    }
362    let ctx = Ctx { interfaces, known, maps, types_from: idl.directive("rust-types-from") };
363
364    let mut out = String::new();
365    let w = &mut out;
366    let rt = &options.runtime;
367
368    writeln!(w, "// @generated by microcom-codegen. Do not edit.\n").unwrap();
369    writeln!(
370        w,
371        "#[allow(unused_imports)]\nuse {rt}::{{make_com, ComObject, ComPtr, Guid, HResult, IUnknown, IUnknownImpl, IUnknownVtbl, ImplementedBy, Interface, RawHResult, S_OK}};\n"
372    )
373    .unwrap();
374
375    if let Some(path) = ctx.types_from {
376        writeln!(w, "#[allow(unused_imports)]\nuse {path}::*;\n").unwrap();
377    }
378
379    for e in &idl.enums {
380        gen_enum(w, e)?;
381    }
382    for s in &idl.structs {
383        writeln!(w, "#[repr(C)]\n#[derive(Clone, Copy, Debug, PartialEq)]\npub struct {} {{", s.name).unwrap();
384        for f in &s.fields {
385            if f.ty.reference {
386                return Err(format!("{}.{}: reference fields are not supported", s.name, f.name));
387            }
388            writeln!(w, "    pub {}: {},", snake_case(&f.name), ctx.raw_type(&f.ty, false)?).unwrap();
389        }
390        writeln!(w, "}}\n").unwrap();
391    }
392    for i in &idl.interfaces {
393        gen_interface(w, &ctx, i).map_err(|e| format!("{}: {e}", i.name))?;
394    }
395    Ok(out)
396}
397
398fn gen_enum(w: &mut String, e: &Enum) -> Result<(), String> {
399    writeln!(
400        w,
401        "#[repr(transparent)]\n#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]\npub struct {}(pub i32);\n",
402        e.name
403    )
404    .unwrap();
405    writeln!(w, "#[allow(non_upper_case_globals)]\nimpl {} {{", e.name).unwrap();
406    let mut known: HashMap<String, i64> = HashMap::new();
407    let mut next = 0i64;
408    let mut names: Vec<(i64, &str)> = Vec::new();
409    let mut seen = HashSet::new();
410    for m in &e.members {
411        let value = match &m.value {
412            Some(expr) => eval_enum_expr(expr, &known)
413                .ok_or_else(|| format!("{}::{}: cannot evaluate '{expr}'", e.name, m.name))?,
414            None => next,
415        };
416        if i32::try_from(value).is_err() {
417            return Err(format!("{}::{}: value {value} does not fit in int", e.name, m.name));
418        }
419        next = value + 1;
420        known.insert(m.name.clone(), value);
421        if seen.insert(value) {
422            names.push((value, &m.name));
423        }
424        writeln!(w, "    pub const {}: Self = Self({value});", m.name).unwrap();
425    }
426    writeln!(w, "\n    /// Name of the (first) member with this value, if any.").unwrap();
427    writeln!(w, "    pub fn name(self) -> Option<&'static str> {{\n        Some(match self.0 {{").unwrap();
428    for (v, n) in &names {
429        writeln!(w, "            {v} => \"{n}\",").unwrap();
430    }
431    writeln!(w, "            _ => return None,\n        }})\n    }}").unwrap();
432    writeln!(w, "\n    /// `true` if all bits of `other` are set in `self`.").unwrap();
433    writeln!(w, "    pub const fn contains(self, other: Self) -> bool {{\n        self.0 & other.0 == other.0\n    }}\n}}\n").unwrap();
434    let n = &e.name;
435    writeln!(
436        w,
437        "impl ::core::fmt::Debug for {n} {{\n    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {{\n        match self.name() {{\n            Some(n) => f.write_str(n),\n            None => write!(f, \"{n}({{}})\", self.0),\n        }}\n    }}\n}}\n"
438    )
439    .unwrap();
440    writeln!(
441        w,
442        "impl ::core::ops::BitOr for {n} {{\n    type Output = Self;\n    fn bitor(self, rhs: Self) -> Self {{\n        Self(self.0 | rhs.0)\n    }}\n}}\n\nimpl ::core::ops::BitAnd for {n} {{\n    type Output = Self;\n    fn bitand(self, rhs: Self) -> Self {{\n        Self(self.0 & rhs.0)\n    }}\n}}\n"
443    )
444    .unwrap();
445    Ok(())
446}
447
448fn gen_interface(w: &mut String, ctx: &Ctx, i: &Interface) -> Result<(), String> {
449    let name = &i.name;
450    let base = i.base_name();
451    if !ctx.is_interface(base) {
452        return Err(format!("unknown base interface '{base}'"));
453    }
454    let uuid = parse_uuid(i.uuid().ok_or("missing uuid")?)?;
455    let mut seen = HashSet::new();
456    let mut methods = Vec::new();
457    // A property has two methods of one name: the one that sets it (`[propput]`) is `SetName`,
458    // as in the bindings of the reference generator; the one that reads it keeps the name.
459    let renamed: Vec<Method> = i
460        .methods
461        .iter()
462        .map(|m| {
463            let mut m = m.clone();
464            if has_attr(&m.attributes, "propput") {
465                m.name = format!("Set{}", m.name);
466            }
467            m
468        })
469        .collect();
470    for m in &renamed {
471        if !seen.insert(m.name.as_str()) {
472            return Err(format!("overloaded method '{}' is not supported", m.name));
473        }
474        methods.push(analyze(ctx, m).map_err(|e| format!("{}: {e}", m.name))?);
475    }
476    let this = "self as *const Self as *mut ::core::ffi::c_void";
477
478    // --- vtable -----------------------------------------------------------
479    writeln!(w, "#[repr(C)]\n#[allow(non_snake_case)]\npub struct {name}Vtbl {{\n    pub base: {base}Vtbl,").unwrap();
480    for m in &methods {
481        writeln!(
482            w,
483            "    pub {}: unsafe extern \"system\" fn(this: *mut ::core::ffi::c_void{}){},",
484            m.method.name,
485            m.raw_params(),
486            m.raw_ret
487        )
488        .unwrap();
489    }
490    writeln!(w, "}}\n").unwrap();
491
492    // --- interface type ---------------------------------------------------
493    writeln!(w, "#[repr(C)]\npub struct {name} {{\n    vtbl: *const {name}Vtbl,\n}}\n").unwrap();
494    writeln!(
495        w,
496        "unsafe impl Interface for {name} {{\n    const IID: Guid = Guid::from_u128(0x{uuid:032x});\n    const NAME: &'static str = \"{name}\";\n    type Vtbl = {name}Vtbl;\n\n    fn matches_iid(iid: &Guid) -> bool {{\n        *iid == Self::IID\n            || *iid == Self::IID.as_com_h_materialized()\n            || <{base} as Interface>::matches_iid(iid)\n    }}\n}}\n"
497    )
498    .unwrap();
499    writeln!(
500        w,
501        "impl ::core::ops::Deref for {name} {{\n    type Target = {base};\n    #[inline]\n    fn deref(&self) -> &{base} {{\n        unsafe {{ &*(self as *const Self as *const {base}) }}\n    }}\n}}\n"
502    )
503    .unwrap();
504
505    // --- proxy methods ----------------------------------------------------
506    writeln!(w, "impl {name} {{").unwrap();
507    writeln!(
508        w,
509        "    /// Wraps a Rust implementation into a COM object native code can call.\n    pub fn from_impl<T: {name}Impl + 'static>(value: T) -> ComPtr<{name}> {{\n        make_com(value)\n    }}"
510    )
511    .unwrap();
512    for m in &methods {
513        let unsafe_kw = if m.needs_unsafe { "unsafe " } else { "" };
514        writeln!(w, "\n    /// `{}::{}`", name, m.method.name).unwrap();
515        if m.needs_unsafe {
516            writeln!(w, "    ///\n    /// # Safety\n    /// Raw pointer arguments must satisfy the native method's contract.").unwrap();
517        }
518        writeln!(w, "    pub {unsafe_kw}fn {}(&self{}){} {{", m.snake, m.hi_params(), m.hi_ret).unwrap();
519        let mut args = String::new();
520        for p in &m.params {
521            let n = &p.name;
522            match &p.kind {
523                ParamKind::Plain { .. } => write!(args, ", {n}").unwrap(),
524                ParamKind::Interface => {
525                    write!(args, ", {n}.map_or(::core::ptr::null_mut(), |p| p.as_raw())").unwrap()
526                }
527                ParamKind::CStr { is_const: true } => {
528                    write!(args, ", {n}.map_or(::core::ptr::null(), |s| s.as_ptr())").unwrap()
529                }
530                ParamKind::CStr { is_const: false } => {
531                    write!(args, ", {n}.map_or(::core::ptr::null_mut(), |s| s.as_ptr() as *mut _)").unwrap()
532                }
533                ParamKind::ConstRef => write!(args, ", {n} as *const _").unwrap(),
534            }
535        }
536        let call = |extra: &str| format!("((*self.vtbl).{})({this}{args}{extra})", m.method.name);
537        writeln!(w, "        unsafe {{").unwrap();
538        match (&m.ret, &m.out) {
539            (RetKind::Unit, _) => writeln!(w, "            {}", call("")).unwrap(),
540            (RetKind::HResult, None) => writeln!(w, "            HResult::check({})", call("")).unwrap(),
541            (RetKind::HResult, Some((_, _, OutKind::Interface(n)))) => {
542                writeln!(w, "            let mut __out: *mut {n} = ::core::ptr::null_mut();").unwrap();
543                writeln!(w, "            HResult::check({})?;", call(", &mut __out")).unwrap();
544                writeln!(w, "            Ok(ComPtr::from_raw(__out))").unwrap();
545            }
546            (RetKind::HResult, Some((_, _, OutKind::Value(t)))) => {
547                writeln!(w, "            let mut __out = ::core::mem::MaybeUninit::<{t}>::zeroed();").unwrap();
548                writeln!(w, "            HResult::check({})?;", call(", __out.as_mut_ptr()")).unwrap();
549                writeln!(w, "            Ok(__out.assume_init())").unwrap();
550            }
551            (RetKind::Interface(_), _) => writeln!(w, "            ComPtr::from_raw({})", call("")).unwrap(),
552            (RetKind::Plain(_), _) => writeln!(w, "            {}", call("")).unwrap(),
553        }
554        writeln!(w, "        }}\n    }}").unwrap();
555    }
556    writeln!(w, "}}\n").unwrap();
557
558    // --- implementation trait ----------------------------------------------
559    writeln!(w, "/// Implement this to provide `{name}` to native code (see [`{name}::from_impl`]).").unwrap();
560    writeln!(w, "pub trait {name}Impl: {base}Impl {{").unwrap();
561    for m in &methods {
562        writeln!(w, "    fn {}(&self{}){};", m.snake, m.hi_params(), m.hi_ret).unwrap();
563    }
564    writeln!(w, "}}\n").unwrap();
565    for wrapper in ["::std::rc::Rc", "::std::boxed::Box"] {
566        writeln!(w, "impl<T: {name}Impl + ?Sized> {name}Impl for {wrapper}<T> {{").unwrap();
567        for m in &methods {
568            writeln!(
569                w,
570                "    #[inline]\n    fn {}(&self{}){} {{\n        <T as {name}Impl>::{}(&**self{})\n    }}",
571                m.snake,
572                m.hi_params(),
573                m.hi_ret,
574                m.snake,
575                m.forward_args()
576            )
577            .unwrap();
578        }
579        writeln!(w, "}}\n").unwrap();
580    }
581
582    // --- thunks + vtable constructor ----------------------------------------
583    writeln!(w, "#[allow(non_snake_case)]\nmod {name}_thunks {{\n    #[allow(unused_imports)]\n    use super::*;").unwrap();
584    for m in &methods {
585        writeln!(
586            w,
587            "\n    pub unsafe extern \"system\" fn {}<T: {name}Impl>(__this: *mut ::core::ffi::c_void{}){} {{",
588            m.method.name,
589            m.raw_params(),
590            m.raw_ret
591        )
592        .unwrap();
593        let mut args = String::new();
594        for p in &m.params {
595            let n = &p.name;
596            match &p.kind {
597                ParamKind::Plain { .. } => write!(args, ", {n}").unwrap(),
598                ParamKind::Interface => write!(args, ", {n}.as_ref()").unwrap(),
599                ParamKind::CStr { .. } => write!(
600                    args,
601                    ", if {n}.is_null() {{ None }} else {{ Some(::core::ffi::CStr::from_ptr({n})) }}"
602                )
603                .unwrap(),
604                ParamKind::ConstRef => write!(args, ", &*{n}").unwrap(),
605            }
606        }
607        let call = format!("<T as {name}Impl>::{}(ComObject::<T>::value(__this){args})", m.snake);
608        match (&m.ret, &m.out) {
609            (RetKind::Unit, _) | (RetKind::Plain(_), _) => writeln!(w, "        {call}").unwrap(),
610            (RetKind::HResult, None) => writeln!(w, "        HResult::from_result({call})").unwrap(),
611            (RetKind::HResult, Some((n, _, kind))) => {
612                let conv = match kind {
613                    OutKind::Interface(_) => "__v.map_or(::core::ptr::null_mut(), ComPtr::into_raw)",
614                    OutKind::Value(_) => "__v",
615                };
616                writeln!(
617                    w,
618                    "        match {call} {{\n            Ok(__v) => {{\n                if !{n}.is_null() {{\n                    *{n} = {conv};\n                }}\n                S_OK\n            }}\n            Err(__e) => __e.0,\n        }}"
619                )
620                .unwrap();
621            }
622            (RetKind::Interface(_), _) => {
623                writeln!(w, "        {call}.map_or(::core::ptr::null_mut(), ComPtr::into_raw)").unwrap()
624            }
625        }
626        writeln!(w, "    }}").unwrap();
627    }
628    writeln!(w, "}}\n").unwrap();
629
630    writeln!(
631        w,
632        "impl {name}Vtbl {{\n    /// Vtable for a `ComObject<T>` whose most-derived interface is `I`.\n    pub const fn new<I: Interface, T: {name}Impl>() -> Self {{\n        Self {{\n            base: {base}Vtbl::new::<I, T>(),"
633    )
634    .unwrap();
635    for m in &methods {
636        writeln!(w, "            {0}: {name}_thunks::{0}::<T>,", m.method.name).unwrap();
637    }
638    writeln!(w, "        }}\n    }}\n}}\n").unwrap();
639    writeln!(
640        w,
641        "unsafe impl<T: {name}Impl> ImplementedBy<T> for {name} {{\n    const VTBL: &'static {name}Vtbl = &{name}Vtbl::new::<{name}, T>();\n}}\n"
642    )
643    .unwrap();
644    Ok(())
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650
651    #[test]
652    fn snake() {
653        assert_eq!(snake_case("GetClientSize"), "get_client_size");
654        assert_eq!(snake_case("texImageIOSurface2D"), "tex_image_io_surface2d");
655        assert_eq!(snake_case("RootProvider_GetWindow"), "root_provider_get_window");
656        assert_eq!(snake_case("GetIOKitRegistryId"), "get_io_kit_registry_id");
657        assert_eq!(snake_case("ObtainNSViewHandle"), "obtain_ns_view_handle");
658        assert_eq!(snake_case("type"), "type_");
659        assert_eq!(snake_case("retOut"), "ret_out");
660    }
661
662    #[test]
663    fn enum_values() {
664        let mut known = HashMap::new();
665        known.insert("A".to_string(), 1);
666        known.insert("B".to_string(), 4);
667        assert_eq!(eval_enum_expr("-1", &known), Some(-1));
668        assert_eq!(eval_enum_expr("0x10", &known), Some(16));
669        assert_eq!(eval_enum_expr("A | B", &known), Some(5));
670        assert_eq!(eval_enum_expr("1 << 3", &known), Some(8));
671        assert_eq!(eval_enum_expr("nope", &known), None);
672    }
673
674    #[test]
675    fn mapped_types_and_out_parameters() {
676        let idl = crate::parse(
677            r#"
678            @rust-map HWND isize
679            @rust-map UINT u32
680            @rust-map REFIID *const Guid
681            @rust-types-from super
682            [uuid(00000000-0000-0000-0000-000000000002)]
683            interface IB : IUnknown {
684                HRESULT GetParent([in] REFIID riid, [out, retval] void** ppParent);
685                HRESULT GetDesc([out] DESC* pDesc);
686                HRESULT GetHwnd([out] HWND* pHwnd);
687                HRESULT GetOutput([out] IB** ppOutput);
688                HRESULT Resize([in] UINT Width, [in] DESC* pDesc);
689                INT32 Enum([in] UINT index, [out] void** ppAdapter);
690            }"#,
691        )
692        .unwrap();
693        let rs = generate(&idl, &RustOptions::default()).unwrap();
694        assert!(rs.contains("pub unsafe fn get_parent(&self, riid: *const Guid) -> Result<*mut ::core::ffi::c_void, HResult>"));
695        assert!(rs.contains("pub fn get_desc(&self) -> Result<DESC, HResult>"));
696        assert!(rs.contains("pub fn get_hwnd(&self) -> Result<isize, HResult>"));
697        assert!(rs.contains("pub fn get_output(&self) -> Result<Option<ComPtr<IB>>, HResult>"));
698        assert!(rs.contains("pub unsafe fn resize(&self, width: u32, p_desc: *mut DESC) -> Result<(), HResult>"));
699        // Not an HRESULT: the out parameter stays a parameter.
700        assert!(rs.contains("pub unsafe fn enum_(&self, index: u32, pp_adapter: *mut *mut ::core::ffi::c_void) -> INT32"));
701        assert!(rs.contains("use super::*;"));
702        assert!(rs.contains("pub GetHwnd: unsafe extern \"system\" fn(this: *mut ::core::ffi::c_void, p_hwnd: *mut isize) -> RawHResult,"));
703    }
704
705    #[test]
706    fn the_two_methods_of_a_property() {
707        let idl = crate::parse(
708            r#"
709            [uuid(00000000-0000-0000-0000-000000000004)]
710            interface ID : IUnknown {
711                [propget] HRESULT Opacity([out] [retval] float* value);
712                [propput] HRESULT Opacity([in] float value);
713            }"#,
714        )
715        .unwrap();
716        let rs = generate(&idl, &RustOptions::default()).unwrap();
717        assert!(rs.contains("pub fn opacity(&self) -> Result<f32, HResult>"));
718        assert!(rs.contains("pub fn set_opacity(&self, value: f32) -> Result<(), HResult>"));
719    }
720
721    #[test]
722    fn an_unknown_type_without_a_place_to_look_is_an_error() {
723        let idl = crate::parse(
724            "[uuid(00000000-0000-0000-0000-000000000003)] interface IC : IUnknown { HRESULT Get(DESC* pDesc); }",
725        )
726        .unwrap();
727        assert_eq!(Err("IC: Get: unknown type 'DESC'".to_string()), generate(&idl, &RustOptions::default()));
728    }
729
730    #[test]
731    fn signatures() {
732        let idl = crate::parse(
733            r#"
734            struct S { int A; }
735            [uuid(00000000-0000-0000-0000-000000000001)]
736            interface IA : IUnknown {
737                HRESULT Get(int index, IA** ppv);
738                HRESULT Size(S* ret);
739                HRESULT SetFrame(S* fb);
740                IA* Parent();
741                bool Key(int type, [const] char* text, [const] S& s, IA* other);
742            }"#,
743        )
744        .unwrap();
745        let rs = generate(&idl, &RustOptions::default()).unwrap();
746        assert!(rs.contains("pub fn get(&self, index: i32) -> Result<Option<ComPtr<IA>>, HResult>"));
747        assert!(rs.contains("pub fn size(&self) -> Result<S, HResult>"));
748        assert!(rs.contains("pub unsafe fn set_frame(&self, fb: *mut S) -> Result<(), HResult>"));
749        assert!(rs.contains("pub fn parent(&self) -> Option<ComPtr<IA>>"));
750        assert!(rs.contains(
751            "pub fn key(&self, type_: i32, text: Option<&::core::ffi::CStr>, s: &S, other: Option<&IA>) -> bool"
752        ));
753        assert!(rs.contains(
754            "pub Key: unsafe extern \"system\" fn(this: *mut ::core::ffi::c_void, type_: i32, text: *const ::core::ffi::c_char, s: *const S, other: *mut IA) -> bool,"
755        ));
756    }
757}