libgir/codegen/sys/
ffi_type.rs

1use log::{info, trace, warn};
2
3use crate::{
4    analysis::{
5        c_type::{implements_c_type, rustify_pointers},
6        namespaces,
7        rust_type::{Result, TypeError},
8    },
9    env::Env,
10    library::{self, *},
11    traits::*,
12};
13
14// FIXME: This module needs redundant allocations audit
15// TODO: ffi_type computations should be cached
16
17pub fn ffi_type(env: &Env, tid: library::TypeId, c_type: &str) -> Result {
18    let (ptr, inner) = rustify_pointers(c_type);
19    let res = if ptr.is_empty() {
20        if let Some(c_tid) = env.library.find_type(0, c_type) {
21            // Fast track plain basic types avoiding some checks
22            if env.library.type_(c_tid).maybe_ref_as::<Basic>().is_some() {
23                match *env.library.type_(tid) {
24                    Type::FixedArray(inner_tid, size, ref inner_c_type) => {
25                        let inner_c_type = inner_c_type.as_ref().map_or(c_type, String::as_str);
26                        ffi_type(env, inner_tid, inner_c_type).map_any(|rust_type| {
27                            rust_type.alter_type(|typ_| format!("[{typ_}; {size}]"))
28                        })
29                    }
30                    Type::Class(Class {
31                        c_type: ref expected,
32                        ..
33                    })
34                    | Type::Interface(Interface {
35                        c_type: ref expected,
36                        ..
37                    }) if c_type == "gpointer" => {
38                        info!("[c:type `gpointer` instead of `*mut {}`, fixing]", expected);
39                        ffi_inner(env, tid, expected.clone()).map_any(|rust_type| {
40                            rust_type.alter_type(|typ_| format!("*mut {typ_}"))
41                        })
42                    }
43                    _ => ffi_inner(env, c_tid, c_type.into()),
44                }
45            } else {
46                // c_type isn't Basic
47                ffi_inner(env, tid, inner)
48            }
49        } else {
50            // c_type doesn't match any type in the library by name
51            ffi_inner(env, tid, inner)
52        }
53    } else {
54        // ptr not empty
55        ffi_inner(env, tid, inner)
56            .map_any(|rust_type| rust_type.alter_type(|typ_| format!("{ptr} {typ_}")))
57    };
58    trace!("ffi_type({:?}, {}) -> {:?}", tid, c_type, res);
59    res
60}
61
62fn ffi_inner(env: &Env, tid: library::TypeId, mut inner: String) -> Result {
63    let volatile = inner.starts_with("volatile ");
64    if volatile {
65        inner = inner["volatile ".len()..].into();
66    }
67
68    let typ = env.library.type_(tid);
69    let res = match *typ {
70        Type::Basic(fund) => {
71            use crate::library::Basic::*;
72            let inner = match fund {
73                None => "c_void",
74                Boolean => "gboolean",
75                Int8 => "i8",
76                UInt8 => "u8",
77                Int16 => "i16",
78                UInt16 => "u16",
79                Int32 => "i32",
80                UInt32 => "u32",
81                Int64 => "i64",
82                UInt64 => "u64",
83                Char => "c_char",
84                UChar => "c_uchar",
85                Short => "c_short",
86                UShort => "c_ushort",
87                Int => "c_int",
88                UInt => "c_uint",
89                Long => "c_long",
90                ULong => "c_ulong",
91                Size => "size_t",
92                SSize => "ssize_t",
93                TimeT => "time_t",
94                OffT => "off_t",
95                DevT => "dev_t",
96                GidT => "gid_t",
97                PidT => "pid_t",
98                SockLenT => "socklen_t",
99                UidT => "uid_t",
100                Float => "c_float",
101                Double => "c_double",
102                UniChar => "u32",
103                Utf8 => "c_char",
104                Filename => "c_char",
105                OsString => "c_char",
106                Type => "GType",
107                Pointer => {
108                    match inner.as_str() {
109                        "void" => "c_void",
110                        // TODO: try use time:Tm
111                        "tm" => return Err(TypeError::Unimplemented(inner)),
112                        _ => &*inner,
113                    }
114                }
115                IntPtr => "intptr_t",
116                UIntPtr => "uintptr_t",
117                Bool => "bool",
118                Unsupported => return Err(TypeError::Unimplemented(inner)),
119                VarArgs => panic!("Should not reach here"),
120            };
121            Ok(inner.into())
122        }
123        Type::Record(..) | Type::Alias(..) | Type::Function(..) => {
124            if let Some(declared_c_type) = typ.get_glib_name() {
125                if declared_c_type != inner {
126                    let msg = format!(
127                        "[c:type mismatch `{}` != `{}` of `{}`]",
128                        inner,
129                        declared_c_type,
130                        typ.get_name()
131                    );
132                    warn!("{}", msg);
133                    return Err(TypeError::Mismatch(msg));
134                }
135            } else {
136                warn!("Type `{}` missing c_type", typ.get_name());
137            }
138            fix_name(env, tid, &inner)
139        }
140        Type::CArray(inner_tid) => ffi_inner(env, inner_tid, inner),
141        Type::FixedArray(inner_tid, size, ref inner_c_type) => {
142            let inner_c_type = inner_c_type
143                .as_ref()
144                .map_or_else(|| inner.as_str(), String::as_str);
145            ffi_type(env, inner_tid, inner_c_type)
146                .map_any(|rust_type| rust_type.alter_type(|typ_| format!("[{typ_}; {size}]")))
147        }
148        Type::Array(..)
149        | Type::PtrArray(..)
150        | Type::List(..)
151        | Type::SList(..)
152        | Type::HashTable(..) => fix_name(env, tid, &inner),
153        _ => {
154            if let Some(glib_name) = env.library.type_(tid).get_glib_name() {
155                if inner != glib_name {
156                    if inner == "gpointer" {
157                        fix_name(env, tid, glib_name).map_any(|rust_type| {
158                            rust_type.alter_type(|typ_| format!("*mut {typ_}"))
159                        })
160                    } else if implements_c_type(env, tid, &inner) {
161                        info!(
162                            "[c:type {} of {} <: {}, fixing]",
163                            glib_name,
164                            env.library.type_(tid).get_name(),
165                            inner
166                        );
167                        fix_name(env, tid, glib_name)
168                    } else {
169                        let msg = format!(
170                            "[c:type mismatch {} != {} of {}]",
171                            inner,
172                            glib_name,
173                            env.library.type_(tid).get_name()
174                        );
175                        warn!("{}", msg);
176                        Err(TypeError::Mismatch(msg))
177                    }
178                } else {
179                    fix_name(env, tid, &inner)
180                }
181            } else {
182                let msg = format!(
183                    "[Missing glib_name of {}, can't match != {}]",
184                    env.library.type_(tid).get_name(),
185                    inner
186                );
187                warn!("{}", msg);
188                Err(TypeError::Mismatch(msg))
189            }
190        }
191    };
192
193    if volatile {
194        res.map(|rust_type| rust_type.alter_type(|typ_| format!("/*volatile*/{typ_}")))
195    } else {
196        res
197    }
198}
199
200fn fix_name(env: &Env, type_id: library::TypeId, name: &str) -> Result {
201    if type_id.ns_id == library::INTERNAL_NAMESPACE {
202        match env.library.type_(type_id) {
203            Type::Array(..)
204            | Type::PtrArray(..)
205            | Type::List(..)
206            | Type::SList(..)
207            | Type::HashTable(..) => {
208                if env.namespaces.glib_ns_id == namespaces::MAIN {
209                    Ok(name.into())
210                } else {
211                    Ok(format!(
212                        "{}::{}",
213                        &env.namespaces[env.namespaces.glib_ns_id].crate_name, name
214                    )
215                    .into())
216                }
217            }
218            _ => Ok(name.into()),
219        }
220    } else {
221        let name_with_prefix = if type_id.ns_id == namespaces::MAIN {
222            name.into()
223        } else {
224            format!("{}::{}", &env.namespaces[type_id.ns_id].crate_name, name)
225        };
226        if env
227            .type_status_sys(&type_id.full_name(&env.library))
228            .ignored()
229        {
230            Err(TypeError::Ignored(name_with_prefix))
231        } else {
232            Ok(name_with_prefix.into())
233        }
234    }
235}