1use std::collections::{HashMap, HashSet};
50use std::fmt::Write;
51
52use crate::ast::*;
53
54#[derive(Debug, Clone)]
55pub struct RustOptions {
56 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
76pub 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 maps: HashMap<&'a str, &'a str>,
133 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 Ok(name.to_string())
152 } else {
153 Err(format!("unknown type '{name}'"))
154 }
155 }
156
157 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 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
173enum ParamKind {
175 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 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 (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
315fn 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 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 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 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 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 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 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 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}