1use std::{borrow::Borrow, result};
2
3use super::conversion_type::ConversionType;
4use crate::{
5 analysis::{record_type::RecordType, ref_mode::RefMode, try_from_glib::TryFromGlib},
6 config::functions::{CallbackParameter, CallbackParameters},
7 env::Env,
8 library::{self, Nullable, ParameterDirection, ParameterScope},
9 nameutil::{is_gstring, use_glib_type},
10 traits::*,
11};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum TypeError {
15 Ignored(String),
16 Mismatch(String),
17 Unimplemented(String),
18}
19
20#[derive(Clone, Debug, Default, PartialEq, Eq)]
22pub struct RustType {
23 inner: String,
24 used_types: Vec<String>,
25}
26
27impl RustType {
28 pub fn try_new(env: &Env, type_id: library::TypeId) -> Result {
30 RustTypeBuilder::new(env, type_id).try_build()
31 }
32
33 pub fn builder(env: &Env, type_id: library::TypeId) -> RustTypeBuilder<'_> {
36 RustTypeBuilder::new(env, type_id)
37 }
38
39 fn new_and_use(rust_type: &impl ToString) -> Self {
40 RustType {
41 inner: rust_type.to_string(),
42 used_types: vec![rust_type.to_string()],
43 }
44 }
45
46 fn new_with_uses(rust_type: &impl ToString, uses: &[impl ToString]) -> Self {
47 RustType {
48 inner: rust_type.to_string(),
49 used_types: uses.iter().map(ToString::to_string).collect(),
50 }
51 }
52
53 fn check(
54 env: &Env,
55 type_id: library::TypeId,
56 type_name: &impl ToString,
57 ) -> result::Result<String, TypeError> {
58 let mut type_name = type_name.to_string();
59
60 if type_id.ns_id != library::MAIN_NAMESPACE
61 && type_id.ns_id != library::INTERNAL_NAMESPACE
62 && type_id.full_name(&env.library) != "GLib.DestroyNotify"
63 && type_id.full_name(&env.library) != "GObject.Callback"
64 {
65 type_name = format!(
66 "{}::{}",
67 env.namespaces[type_id.ns_id].higher_crate_name,
68 type_name.as_str()
69 );
70
71 if env.type_status(&type_id.full_name(&env.library)).ignored() {
72 return Err(TypeError::Ignored(type_name));
73 }
74 }
75
76 Ok(type_name)
77 }
78
79 fn try_new_and_use(env: &Env, type_id: library::TypeId) -> Result {
80 Self::check(env, type_id, &env.library.type_(type_id).get_name()).map(|type_name| {
81 RustType {
82 inner: type_name.clone(),
83 used_types: vec![type_name],
84 }
85 })
86 }
87
88 fn try_new_and_use_with_name(
89 env: &Env,
90 type_id: library::TypeId,
91 type_name: impl ToString,
92 ) -> Result {
93 Self::check(env, type_id, &type_name).map(|type_name| RustType {
94 inner: type_name.clone(),
95 used_types: vec![type_name],
96 })
97 }
98
99 pub fn used_types(&self) -> &Vec<String> {
100 &self.used_types
101 }
102
103 pub fn into_used_types(self) -> Vec<String> {
104 self.used_types
105 }
106
107 pub fn as_str(&self) -> &str {
108 self.inner.as_str()
109 }
110
111 #[inline]
112 pub fn alter_type(mut self, op: impl FnOnce(String) -> String) -> Self {
113 self.inner = op(self.inner);
114 self
115 }
116
117 #[inline]
118 fn format_parameter(self, direction: ParameterDirection) -> Self {
119 if direction.is_out() {
120 self.alter_type(|type_| format!("&mut {type_}"))
121 } else {
122 self
123 }
124 }
125
126 #[inline]
127 fn apply_ref_mode(self, ref_mode: RefMode) -> Self {
128 match ref_mode.for_rust_type() {
129 "" => self,
130 ref_mode => self.alter_type(|typ_| format!("{ref_mode}{typ_}")),
131 }
132 }
133}
134
135impl<T: ToString> From<T> for RustType {
136 fn from(rust_type: T) -> Self {
137 RustType {
138 inner: rust_type.to_string(),
139 used_types: Vec::new(),
140 }
141 }
142}
143
144impl IntoString for RustType {
145 fn into_string(self) -> String {
146 self.inner
147 }
148}
149
150pub type Result = result::Result<RustType, TypeError>;
151
152fn into_inner(res: Result) -> String {
153 use self::TypeError::*;
154 match res {
155 Ok(rust_type) => rust_type.into_string(),
156 Err(Ignored(s) | Mismatch(s) | Unimplemented(s)) => s,
157 }
158}
159
160impl IntoString for Result {
161 fn into_string(self) -> String {
162 use self::TypeError::*;
163 match self {
164 Ok(s) => s.into_string(),
165 Err(Ignored(s)) => format!("/*Ignored*/{s}"),
166 Err(Mismatch(s)) => format!("/*Metadata mismatch*/{s}"),
167 Err(Unimplemented(s)) => format!("/*Unimplemented*/{s}"),
168 }
169 }
170}
171
172impl MapAny<RustType> for Result {
173 fn map_any<F: FnOnce(RustType) -> RustType>(self, op: F) -> Result {
174 use self::TypeError::*;
175 match self {
176 Ok(rust_type) => Ok(op(rust_type)),
177 Err(Ignored(s)) => Err(Ignored(op(s.into()).into_string())),
178 Err(Mismatch(s)) => Err(Mismatch(op(s.into()).into_string())),
179 Err(Unimplemented(s)) => Err(Unimplemented(op(s.into()).into_string())),
180 }
181 }
182}
183
184pub struct RustTypeBuilder<'env> {
185 env: &'env Env,
186 type_id: library::TypeId,
187 direction: ParameterDirection,
188 nullable: Nullable,
189 ref_mode: RefMode,
190 scope: ParameterScope,
191 concurrency: library::Concurrency,
192 try_from_glib: TryFromGlib,
193 callback_parameters_config: CallbackParameters,
194}
195
196impl<'env> RustTypeBuilder<'env> {
197 fn new(env: &'env Env, type_id: library::TypeId) -> Self {
198 Self {
199 env,
200 type_id,
201 direction: ParameterDirection::None,
202 nullable: Nullable(false),
203 ref_mode: RefMode::None,
204 scope: ParameterScope::None,
205 concurrency: library::Concurrency::None,
206 try_from_glib: TryFromGlib::default(),
207 callback_parameters_config: Vec::new(),
208 }
209 }
210
211 pub fn direction(mut self, direction: ParameterDirection) -> Self {
212 self.direction = direction;
213 self
214 }
215
216 pub fn nullable(mut self, nullable: Nullable) -> Self {
217 self.nullable = nullable;
218 self
219 }
220
221 pub fn ref_mode(mut self, ref_mode: RefMode) -> Self {
222 self.ref_mode = ref_mode;
223 self
224 }
225
226 pub fn scope(mut self, scope: ParameterScope) -> Self {
227 self.scope = scope;
228 self
229 }
230
231 pub fn concurrency(mut self, concurrency: library::Concurrency) -> Self {
232 self.concurrency = concurrency;
233 self
234 }
235
236 pub fn try_from_glib(mut self, try_from_glib: &TryFromGlib) -> Self {
237 self.try_from_glib = try_from_glib.clone();
238 self
239 }
240
241 pub fn callback_parameters_config(
242 mut self,
243 callback_parameters_config: &[CallbackParameter],
244 ) -> Self {
245 self.callback_parameters_config = callback_parameters_config.to_owned();
246 self
247 }
248
249 pub fn try_build(self) -> Result {
250 use crate::library::{Basic::*, Type::*};
251 let ok = |s: &str| Ok(RustType::from(s));
252 let ok_and_use = |s: &str| Ok(RustType::new_and_use(&s));
253 let err = |s: &str| Err(TypeError::Unimplemented(s.into()));
254 let mut skip_option = false;
255 let type_ = self.env.library.type_(self.type_id);
256 let mut rust_type = match *type_ {
257 Basic(fund) => {
258 match fund {
259 None => err("()"),
260 Boolean | Bool => ok("bool"),
261 Int8 => ok("i8"),
262 UInt8 => ok("u8"),
263 Int16 => ok("i16"),
264 UInt16 => ok("u16"),
265 Int32 => ok("i32"),
266 UInt32 => ok("u32"),
267 Int64 => ok("i64"),
268 UInt64 => ok("u64"),
269
270 Int => ok("i32"), UInt => ok("u32"), Short => ok_and_use("libc::c_short"), UShort => ok_and_use("libc::c_ushort"), Long => ok_and_use("libc::c_long"), ULong => ok_and_use("libc::c_ulong"), TimeT => ok_and_use("libc::time_t"), OffT => ok_and_use("libc::off_t"), DevT => ok_and_use("libc::dev_t"), GidT => ok_and_use("libc::gid_t"), PidT => ok_and_use("libc::pid_t"), SockLenT => ok_and_use("libc::socklen_t"), UidT => ok_and_use("libc::uid_t"), Size => ok("usize"), SSize => ok("isize"), Float => ok("f32"),
290 Double => ok("f64"),
291
292 UniChar => ok("char"),
293 Utf8 => {
294 if self.ref_mode.is_ref() {
295 ok("str")
296 } else {
297 ok_and_use(&use_glib_type(self.env, "GString"))
298 }
299 }
300 Filename => {
301 if self.ref_mode.is_ref() {
302 ok_and_use("std::path::Path")
303 } else {
304 ok_and_use("std::path::PathBuf")
305 }
306 }
307 OsString => {
308 if self.ref_mode.is_ref() {
309 ok_and_use("std::ffi::OsStr")
310 } else {
311 ok_and_use("std::ffi::OsString")
312 }
313 }
314 Type => ok_and_use(&use_glib_type(self.env, "types::Type")),
315 Char => ok_and_use(&use_glib_type(self.env, "Char")),
316 UChar => ok_and_use(&use_glib_type(self.env, "UChar")),
317 Unsupported => err("Unsupported"),
318 _ => err(&format!("Basic: {fund:?}")),
319 }
320 }
321 Alias(ref alias) => {
322 RustType::try_new_and_use(self.env, self.type_id).and_then(|alias_rust_type| {
323 RustType::builder(self.env, alias.typ)
324 .direction(self.direction)
325 .nullable(self.nullable)
326 .ref_mode(self.ref_mode)
327 .scope(self.scope)
328 .concurrency(self.concurrency)
329 .try_from_glib(&self.try_from_glib)
330 .try_build()
331 .map_any(|_| alias_rust_type)
332 })
333 }
334 Record(library::Record { ref c_type, .. }) if c_type == "GVariantType" => {
335 let type_name = if self.ref_mode.is_ref() {
336 "VariantTy"
337 } else {
338 "VariantType"
339 };
340 RustType::try_new_and_use_with_name(self.env, self.type_id, type_name)
341 }
342 Enumeration(..) | Bitfield(..) | Record(..) | Union(..) | Class(..) | Interface(..) => {
343 RustType::try_new_and_use(self.env, self.type_id).and_then(|rust_type| {
344 if self
345 .env
346 .type_status(&self.type_id.full_name(&self.env.library))
347 .ignored()
348 {
349 Err(TypeError::Ignored(rust_type.into_string()))
350 } else {
351 Ok(rust_type)
352 }
353 })
354 }
355 List(inner_tid) | SList(inner_tid) | CArray(inner_tid) | PtrArray(inner_tid)
356 if ConversionType::of(self.env, inner_tid) == ConversionType::Pointer =>
357 {
358 skip_option = true;
359 let inner_ref_mode = match self.env.type_(inner_tid) {
360 Class(..) | Interface(..) => RefMode::None,
361 Record(record) => match RecordType::of(record) {
362 RecordType::Boxed => RefMode::None,
363 RecordType::AutoBoxed => {
364 if !record.has_copy() {
365 RefMode::None
366 } else {
367 self.ref_mode
368 }
369 }
370 _ => self.ref_mode,
371 },
372 _ => self.ref_mode,
373 };
374 RustType::builder(self.env, inner_tid)
375 .ref_mode(inner_ref_mode)
376 .scope(self.scope)
377 .concurrency(self.concurrency)
378 .try_build()
379 .map_any(|rust_type| {
380 rust_type.alter_type(|typ| {
381 if self.ref_mode.is_ref() {
382 format!("[{typ}]")
383 } else {
384 format!("Vec<{typ}>")
385 }
386 })
387 })
388 }
389 CArray(inner_tid)
390 if ConversionType::of(self.env, inner_tid) == ConversionType::Direct =>
391 {
392 if let Basic(fund) = self.env.type_(inner_tid) {
393 let array_type = match fund {
394 Int8 => Some("i8"),
395 UInt8 => Some("u8"),
396 Int16 => Some("i16"),
397 UInt16 => Some("u16"),
398 Int32 => Some("i32"),
399 UInt32 => Some("u32"),
400 Int64 => Some("i64"),
401 UInt64 => Some("u64"),
402
403 Int => Some("i32"), UInt => Some("u32"), Float => Some("f32"),
407 Double => Some("f64"),
408 _ => Option::None,
409 };
410
411 if let Some(s) = array_type {
412 skip_option = true;
413 if self.ref_mode.is_ref() {
414 Ok(format!("[{s}]").into())
415 } else {
416 Ok(format!("Vec<{s}>").into())
417 }
418 } else {
419 Err(TypeError::Unimplemented(type_.get_name()))
420 }
421 } else {
422 Err(TypeError::Unimplemented(type_.get_name()))
423 }
424 }
425 Custom(library::Custom { ref name, .. }) => {
426 RustType::try_new_and_use_with_name(self.env, self.type_id, name)
427 }
428 Function(ref f) => {
429 let concurrency = match self.concurrency {
430 _ if self.scope.is_call() => "",
431 library::Concurrency::Send => " + Send",
432 library::Concurrency::SendSync => " + Send + Sync",
436 library::Concurrency::None => "",
437 };
438
439 let full_name = self.type_id.full_name(&self.env.library);
440 if full_name == "Gio.AsyncReadyCallback" {
441 return Ok(format!(
443 "FnOnce(Result<(), {}>) + 'static",
444 use_glib_type(self.env, "Error"),
445 )
446 .into());
447 } else if full_name == "GLib.DestroyNotify" {
448 return Ok(format!("Fn(){concurrency} + 'static").into());
449 }
450 let mut params = Vec::with_capacity(f.parameters.len());
451 let mut err = false;
452 for p in &f.parameters {
453 if p.closure.is_some() {
454 continue;
455 }
456
457 let nullable = self
458 .callback_parameters_config
459 .iter()
460 .find(|cp| cp.ident.is_match(&p.name))
461 .and_then(|c| c.nullable)
462 .unwrap_or(p.nullable);
463 let p_res = RustType::builder(self.env, p.typ)
464 .direction(p.direction)
465 .nullable(nullable)
466 .try_build();
467 match p_res {
468 Ok(p_rust_type) => {
469 let is_basic = p.typ.is_basic_type(self.env);
470 let y = RustType::try_new(self.env, p.typ)
471 .unwrap_or_else(|_| RustType::default());
472 params.push(format!(
473 "{}{}",
474 if is_basic || *nullable { "" } else { "&" },
475 if !is_gstring(y.as_str()) {
476 if !is_basic && *nullable {
477 p_rust_type.into_string().replace("Option<", "Option<&")
478 } else {
479 p_rust_type.into_string()
480 }
481 } else if *nullable {
482 "Option<&str>".to_owned()
483 } else {
484 "&str".to_owned()
485 }
486 ));
487 }
488 e => {
489 err = true;
490 params.push(e.into_string());
491 }
492 }
493 }
494 let closure_kind = if self.scope.is_call() {
495 "FnMut"
496 } else if self.scope.is_async() {
497 "FnOnce"
498 } else {
499 "Fn"
500 };
501 let ret_res = RustType::builder(self.env, f.ret.typ)
502 .direction(f.ret.direction)
503 .nullable(f.ret.nullable)
504 .try_build();
505 let ret = match ret_res {
506 Ok(ret_rust_type) => {
507 let y = RustType::try_new(self.env, f.ret.typ)
508 .unwrap_or_else(|_| RustType::default());
509 format!(
510 "{}({}) -> {}{}",
511 closure_kind,
512 params.join(", "),
513 if !is_gstring(y.as_str()) {
514 ret_rust_type.as_str()
515 } else if *f.ret.nullable {
516 "Option<String>"
517 } else {
518 "String"
519 },
520 concurrency
521 )
522 }
523 Err(TypeError::Unimplemented(ref x)) if x == "()" => {
524 format!("{}({}){}", closure_kind, params.join(", "), concurrency)
525 }
526 e => {
527 err = true;
528 format!(
529 "{}({}) -> {}{}",
530 closure_kind,
531 params.join(", "),
532 e.into_string(),
533 concurrency
534 )
535 }
536 };
537 if err {
538 return Err(TypeError::Unimplemented(ret));
539 }
540 Ok(if *self.nullable {
541 if self.scope.is_call() {
542 format!("Option<&mut dyn ({ret})>")
543 } else {
544 format!("Option<Box_<dyn {ret} + 'static>>")
545 }
546 } else {
547 format!(
548 "{}{}",
549 ret,
550 if self.scope.is_call() {
551 ""
552 } else {
553 " + 'static"
554 }
555 )
556 }
557 .into())
558 }
559 _ => Err(TypeError::Unimplemented(type_.get_name())),
560 };
561
562 match self
563 .try_from_glib
564 .or_type_defaults(self.env, self.type_id)
565 .borrow()
566 {
567 TryFromGlib::Option => {
568 rust_type = rust_type.map_any(|rust_type| {
569 rust_type
570 .alter_type(|typ_| {
571 let mut opt = format!("Option<{typ_}>");
572 if self.direction == ParameterDirection::In {
573 opt = format!("impl Into<{opt}>");
574 }
575
576 opt
577 })
578 .apply_ref_mode(self.ref_mode)
579 });
580 }
581 TryFromGlib::Result { ok_type, err_type } => {
582 if self.direction == ParameterDirection::In {
583 rust_type = rust_type.map_any(|rust_type| {
584 RustType::new_with_uses(
585 &format!("impl Into<{}>", &rust_type.as_str()),
586 &[&rust_type.as_str()],
587 )
588 });
589 } else {
590 rust_type = rust_type.map_any(|_| {
591 RustType::new_with_uses(
592 &format!("Result<{}, {}>", &ok_type, &err_type),
593 &[ok_type, err_type],
594 )
595 });
596 }
597 }
598 TryFromGlib::ResultInfallible { ok_type } => {
599 let new_rust_type = RustType::new_and_use(ok_type).apply_ref_mode(self.ref_mode);
600 rust_type = rust_type.map_any(|_| new_rust_type);
601 }
602 _ => {
603 rust_type = rust_type.map_any(|rust_type| rust_type.apply_ref_mode(self.ref_mode));
604 }
605 }
606
607 if *self.nullable && !skip_option {
608 match ConversionType::of(self.env, self.type_id) {
609 ConversionType::Pointer | ConversionType::Scalar => {
610 rust_type = rust_type.map_any(|rust_type| {
611 rust_type.alter_type(|typ_| format!("Option<{typ_}>"))
612 });
613 }
614 _ => (),
615 }
616 }
617
618 rust_type
619 }
620
621 pub fn try_build_param(self) -> Result {
622 use crate::library::Type::*;
623 let type_ = self.env.library.type_(self.type_id);
624
625 assert!(
626 self.direction != ParameterDirection::None,
627 "undefined direction for parameter with type {type_:?}"
628 );
629
630 let rust_type = RustType::builder(self.env, self.type_id)
631 .direction(self.direction)
632 .nullable(self.nullable)
633 .ref_mode(self.ref_mode)
634 .scope(self.scope)
635 .try_from_glib(&self.try_from_glib)
636 .try_build();
637 match type_ {
638 Basic(library::Basic::Utf8 | library::Basic::OsString | library::Basic::Filename)
639 if (self.direction == ParameterDirection::InOut
640 || (self.direction == ParameterDirection::Out
641 && self.ref_mode == RefMode::ByRefMut)) =>
642 {
643 Err(TypeError::Unimplemented(into_inner(rust_type)))
644 }
645 Basic(_) => rust_type.map_any(|rust_type| rust_type.format_parameter(self.direction)),
646
647 Alias(alias) => rust_type
648 .and_then(|rust_type| {
649 RustType::builder(self.env, alias.typ)
650 .direction(self.direction)
651 .nullable(self.nullable)
652 .ref_mode(self.ref_mode)
653 .scope(self.scope)
654 .try_from_glib(&self.try_from_glib)
655 .try_build_param()
656 .map_any(|_| rust_type)
657 })
658 .map_any(|rust_type| rust_type.format_parameter(self.direction)),
659
660 Enumeration(..) | Union(..) | Bitfield(..) => {
661 rust_type.map_any(|rust_type| rust_type.format_parameter(self.direction))
662 }
663
664 Record(..) => {
665 if self.direction == ParameterDirection::InOut {
666 Err(TypeError::Unimplemented(into_inner(rust_type)))
667 } else {
668 rust_type
669 }
670 }
671
672 Class(..) | Interface(..) => match self.direction {
673 ParameterDirection::In | ParameterDirection::Out | ParameterDirection::Return => {
674 rust_type
675 }
676 _ => Err(TypeError::Unimplemented(into_inner(rust_type))),
677 },
678
679 List(..) | SList(..) => match self.direction {
680 ParameterDirection::In | ParameterDirection::Return => rust_type,
681 _ => Err(TypeError::Unimplemented(into_inner(rust_type))),
682 },
683 CArray(..) | PtrArray(..) => match self.direction {
684 ParameterDirection::In | ParameterDirection::Out | ParameterDirection::Return => {
685 rust_type
686 }
687 _ => Err(TypeError::Unimplemented(into_inner(rust_type))),
688 },
689 Function(ref func) if func.name == "AsyncReadyCallback" => {
690 Ok("AsyncReadyCallback".into())
691 }
692 Function(_) => rust_type,
693 Custom(..) => rust_type.map(|rust_type| rust_type.format_parameter(self.direction)),
694 _ => Err(TypeError::Unimplemented(type_.get_name())),
695 }
696 }
697}