libgir/analysis/
rust_type.rs

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/// A `RustType` definition and its associated types to be `use`d.
21#[derive(Clone, Debug, Default, PartialEq, Eq)]
22pub struct RustType {
23    inner: String,
24    used_types: Vec<String>,
25}
26
27impl RustType {
28    /// Try building the `RustType` with no specific additional configuration.
29    pub fn try_new(env: &Env, type_id: library::TypeId) -> Result {
30        RustTypeBuilder::new(env, type_id).try_build()
31    }
32
33    /// Create a `RustTypeBuilder` which allows specifying additional
34    /// configuration.
35    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"),  // maybe dependent on target system
271                    UInt => ok("u32"), // maybe dependent on target system
272
273                    Short => ok_and_use("libc::c_short"), // depends of target system
274                    UShort => ok_and_use("libc::c_ushort"), // depends o f target system
275                    Long => ok_and_use("libc::c_long"),   // depends of target system
276                    ULong => ok_and_use("libc::c_ulong"), // depends of target system
277
278                    TimeT => ok_and_use("libc::time_t"), // depends of target system
279                    OffT => ok_and_use("libc::off_t"),   // depends of target system
280                    DevT => ok_and_use("libc::dev_t"),   // depends of target system
281                    GidT => ok_and_use("libc::gid_t"),   // depends of target system
282                    PidT => ok_and_use("libc::pid_t"),   // depends of target system
283                    SockLenT => ok_and_use("libc::socklen_t"), // depends of target system
284                    UidT => ok_and_use("libc::uid_t"),   // depends of target system
285
286                    Size => ok("usize"),  // depends of target system
287                    SSize => ok("isize"), // depends of target system
288
289                    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"),  // maybe dependent on target system
404                        UInt => Some("u32"), // maybe dependent on target system
405
406                        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                    // If an object is Sync, it can be shared between threads, and as
433                    // such our callback can be called from arbitrary threads and needs
434                    // to be Send *AND* Sync
435                    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                    // FIXME need to use the result from use_glib_type(&self.env, "Error")?
442                    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}