bindgen/ir/
objc.rs

1//! Objective C types
2
3use super::context::{BindgenContext, ItemId};
4use super::function::FunctionSig;
5use super::item::Item;
6use super::traversal::{Trace, Tracer};
7use super::ty::TypeKind;
8use crate::clang;
9use clang_sys::CXChildVisit_Continue;
10use clang_sys::CXCursor_ObjCCategoryDecl;
11use clang_sys::CXCursor_ObjCClassMethodDecl;
12use clang_sys::CXCursor_ObjCClassRef;
13use clang_sys::CXCursor_ObjCInstanceMethodDecl;
14use clang_sys::CXCursor_ObjCProtocolDecl;
15use clang_sys::CXCursor_ObjCProtocolRef;
16use clang_sys::CXCursor_ObjCSuperClassRef;
17use clang_sys::CXCursor_TemplateTypeParameter;
18use proc_macro2::{Ident, Span, TokenStream};
19
20/// Objective-C interface as used in `TypeKind`
21///
22/// Also, protocols and categories are parsed as this type
23#[derive(Debug)]
24pub(crate) struct ObjCInterface {
25    /// The name
26    /// like, `NSObject`
27    name: String,
28
29    category: Option<String>,
30
31    is_protocol: bool,
32
33    /// The list of template names almost always, `ObjectType` or `KeyType`
34    pub(crate) template_names: Vec<String>,
35
36    /// The list of protocols that this interface conforms to.
37    pub(crate) conforms_to: Vec<ItemId>,
38
39    /// The direct parent for this interface.
40    pub(crate) parent_class: Option<ItemId>,
41
42    /// List of the methods defined in this interface
43    methods: Vec<ObjCMethod>,
44
45    class_methods: Vec<ObjCMethod>,
46}
47
48/// The objective c methods
49#[derive(Debug)]
50pub(crate) struct ObjCMethod {
51    /// The original method selector name
52    /// like, dataWithBytes:length:
53    name: String,
54
55    /// Method name as converted to rust
56    /// like, `dataWithBytes_length`_
57    rust_name: String,
58
59    signature: FunctionSig,
60
61    /// Is class method?
62    is_class_method: bool,
63}
64
65impl ObjCInterface {
66    fn new(name: &str) -> ObjCInterface {
67        ObjCInterface {
68            name: name.to_owned(),
69            category: None,
70            is_protocol: false,
71            template_names: Vec::new(),
72            parent_class: None,
73            conforms_to: Vec::new(),
74            methods: Vec::new(),
75            class_methods: Vec::new(),
76        }
77    }
78
79    /// The name
80    /// like, `NSObject`
81    pub(crate) fn name(&self) -> &str {
82        self.name.as_ref()
83    }
84
85    /// Formats the name for rust
86    /// Can be like `NSObject`, but with categories might be like `NSObject_NSCoderMethods`
87    /// and protocols are like `PNSObject`
88    pub(crate) fn rust_name(&self) -> String {
89        if let Some(ref cat) = self.category {
90            format!("{}_{cat}", self.name())
91        } else if self.is_protocol {
92            format!("P{}", self.name())
93        } else {
94            format!("I{}", self.name().to_owned())
95        }
96    }
97
98    /// Is this a template interface?
99    pub(crate) fn is_template(&self) -> bool {
100        !self.template_names.is_empty()
101    }
102
103    /// List of the methods defined in this interface
104    pub(crate) fn methods(&self) -> &Vec<ObjCMethod> {
105        &self.methods
106    }
107
108    /// Is this a protocol?
109    pub(crate) fn is_protocol(&self) -> bool {
110        self.is_protocol
111    }
112
113    /// Is this a category?
114    pub(crate) fn is_category(&self) -> bool {
115        self.category.is_some()
116    }
117
118    /// List of the class methods defined in this interface
119    pub(crate) fn class_methods(&self) -> &Vec<ObjCMethod> {
120        &self.class_methods
121    }
122
123    /// Parses the Objective C interface from the cursor
124    pub(crate) fn from_ty(
125        cursor: &clang::Cursor,
126        ctx: &mut BindgenContext,
127    ) -> Option<Self> {
128        let name = cursor.spelling();
129        let mut interface = Self::new(&name);
130
131        if cursor.kind() == CXCursor_ObjCProtocolDecl {
132            interface.is_protocol = true;
133        }
134
135        cursor.visit(|c| {
136            match c.kind() {
137                CXCursor_ObjCClassRef => {
138                    if cursor.kind() == CXCursor_ObjCCategoryDecl {
139                        // We are actually a category extension, and we found the reference
140                        // to the original interface, so name this interface appropriately
141                        interface.name = c.spelling();
142                        interface.category = Some(cursor.spelling());
143                    }
144                }
145                CXCursor_ObjCProtocolRef => {
146                    // Gather protocols this interface conforms to
147                    let needle = format!("P{}", c.spelling());
148                    let items_map = ctx.items();
149                    debug!(
150                        "Interface {} conforms to {needle}, find the item",
151                        interface.name,
152                    );
153
154                    for (id, item) in items_map {
155                        if let Some(ty) = item.as_type() {
156                            if let TypeKind::ObjCInterface(ref protocol) =
157                                *ty.kind()
158                            {
159                                if protocol.is_protocol {
160                                    debug!(
161                                        "Checking protocol {}, ty.name {:?}",
162                                        protocol.name,
163                                        ty.name()
164                                    );
165                                    if Some(needle.as_ref()) == ty.name() {
166                                        debug!("Found conforming protocol {item:?}");
167                                        interface.conforms_to.push(id);
168                                        break;
169                                    }
170                                }
171                            }
172                        }
173                    }
174                }
175                CXCursor_ObjCInstanceMethodDecl |
176                CXCursor_ObjCClassMethodDecl => {
177                    let name = c.spelling();
178                    let signature =
179                        FunctionSig::from_ty(&c.cur_type(), &c, ctx)
180                            .expect("Invalid function sig");
181                    let is_class_method =
182                        c.kind() == CXCursor_ObjCClassMethodDecl;
183                    let method =
184                        ObjCMethod::new(&name, signature, is_class_method);
185                    interface.add_method(method);
186                }
187                CXCursor_TemplateTypeParameter => {
188                    let name = c.spelling();
189                    interface.template_names.push(name);
190                }
191                CXCursor_ObjCSuperClassRef => {
192                    let item = Item::from_ty_or_ref(c.cur_type(), c, None, ctx);
193                    interface.parent_class = Some(item.into());
194                }
195                _ => {}
196            }
197            CXChildVisit_Continue
198        });
199        Some(interface)
200    }
201
202    fn add_method(&mut self, method: ObjCMethod) {
203        if method.is_class_method {
204            self.class_methods.push(method);
205        } else {
206            self.methods.push(method);
207        }
208    }
209}
210
211impl ObjCMethod {
212    fn new(
213        name: &str,
214        signature: FunctionSig,
215        is_class_method: bool,
216    ) -> ObjCMethod {
217        let split_name: Vec<&str> = name.split(':').collect();
218
219        let rust_name = split_name.join("_");
220
221        ObjCMethod {
222            name: name.to_owned(),
223            rust_name,
224            signature,
225            is_class_method,
226        }
227    }
228
229    /// Method name as converted to rust
230    /// like, `dataWithBytes_length`_
231    pub(crate) fn rust_name(&self) -> &str {
232        self.rust_name.as_ref()
233    }
234
235    /// Returns the methods signature as `FunctionSig`
236    pub(crate) fn signature(&self) -> &FunctionSig {
237        &self.signature
238    }
239
240    /// Is this a class method?
241    pub(crate) fn is_class_method(&self) -> bool {
242        self.is_class_method
243    }
244
245    /// Formats the method call
246    pub(crate) fn format_method_call(
247        &self,
248        args: &[TokenStream],
249    ) -> TokenStream {
250        let split_name: Vec<Option<Ident>> = self
251            .name
252            .split(':')
253            .enumerate()
254            .map(|(idx, name)| {
255                if name.is_empty() {
256                    None
257                } else if idx == 0 {
258                    // Try to parse the method name as an identifier. Having a keyword is ok
259                    // unless it is `crate`, `self`, `super` or `Self`, so we try to add the `_`
260                    // suffix to it and parse it.
261                    if ["crate", "self", "super", "Self"].contains(&name) {
262                        Some(Ident::new(&format!("{name}_"), Span::call_site()))
263                    } else {
264                        Some(Ident::new(name, Span::call_site()))
265                    }
266                } else {
267                    // Try to parse the current joining name as an identifier. This might fail if the name
268                    // is a keyword, so we try to  "r#" to it and parse again, this could also fail
269                    // if the name is `crate`, `self`, `super` or `Self`, so we try to add the `_`
270                    // suffix to it and parse again. If this also fails, we panic with the first
271                    // error.
272                    Some(
273                        syn::parse_str::<Ident>(name)
274                            .or_else(|err| {
275                                syn::parse_str::<Ident>(&format!("r#{name}"))
276                                    .map_err(|_| err)
277                            })
278                            .or_else(|err| {
279                                syn::parse_str::<Ident>(&format!("{name}_"))
280                                    .map_err(|_| err)
281                            })
282                            .expect("Invalid identifier"),
283                    )
284                }
285            })
286            .collect();
287
288        // No arguments
289        if args.is_empty() && split_name.len() == 1 {
290            let name = &split_name[0];
291            return quote! {
292                #name
293            };
294        }
295
296        // Check right amount of arguments
297        assert!(
298            args.len() == split_name.len() - 1,
299            "Incorrect method name or arguments for objc method, {args:?} vs {split_name:?}"
300        );
301
302        // Get arguments without type signatures to pass to `msg_send!`
303        let mut args_without_types = vec![];
304        for arg in args {
305            let arg = arg.to_string();
306            let name_and_sig: Vec<&str> = arg.split(' ').collect();
307            let name = name_and_sig[0];
308            args_without_types.push(Ident::new(name, Span::call_site()));
309        }
310
311        let args = split_name.into_iter().zip(args_without_types).map(
312            |(arg, arg_val)| {
313                if let Some(arg) = arg {
314                    quote! { #arg: #arg_val }
315                } else {
316                    quote! { #arg_val: #arg_val }
317                }
318            },
319        );
320
321        quote! {
322            #( #args )*
323        }
324    }
325}
326
327impl Trace for ObjCInterface {
328    type Extra = ();
329
330    fn trace<T>(&self, context: &BindgenContext, tracer: &mut T, _: &())
331    where
332        T: Tracer,
333    {
334        for method in &self.methods {
335            method.signature.trace(context, tracer, &());
336        }
337
338        for class_method in &self.class_methods {
339            class_method.signature.trace(context, tracer, &());
340        }
341
342        for protocol in &self.conforms_to {
343            tracer.visit(*protocol);
344        }
345    }
346}