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 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 template_names: Vec<String>,
35
36 /// The list of protocols that this interface conforms to.
37 pub conforms_to: Vec<ItemId>,
38
39 /// The direct parent for this interface.
40 pub parent_class: Option<ItemId>,
41
42 /// List of the methods defined in this interfae
43 methods: Vec<ObjCMethod>,
44
45 class_methods: Vec<ObjCMethod>,
46}
47
48/// The objective c methods
49#[derive(Debug)]
50pub 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 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 fn rust_name(&self) -> String {
89 if let Some(ref cat) = self.category {
90 format!("{}_{}", self.name(), cat)
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 fn is_template(&self) -> bool {
100 !self.template_names.is_empty()
101 }
102
103 /// List of the methods defined in this interface
104 pub fn methods(&self) -> &Vec<ObjCMethod> {
105 &self.methods
106 }
107
108 /// Is this a protocol?
109 pub fn is_protocol(&self) -> bool {
110 self.is_protocol
111 }
112
113 /// Is this a category?
114 pub fn is_category(&self) -> bool {
115 self.category.is_some()
116 }
117
118 /// List of the class methods defined in this interface
119 pub fn class_methods(&self) -> &Vec<ObjCMethod> {
120 &self.class_methods
121 }
122
123 /// Parses the Objective C interface from the cursor
124 pub 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 approriately
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 {}, find the item",
151 interface.name, needle
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!(
167 "Found conforming protocol {:?}",
168 item
169 );
170 interface.conforms_to.push(id);
171 break;
172 }
173 }
174 }
175 }
176 }
177 }
178 CXCursor_ObjCInstanceMethodDecl |
179 CXCursor_ObjCClassMethodDecl => {
180 let name = c.spelling();
181 let signature =
182 FunctionSig::from_ty(&c.cur_type(), &c, ctx)
183 .expect("Invalid function sig");
184 let is_class_method =
185 c.kind() == CXCursor_ObjCClassMethodDecl;
186 let method =
187 ObjCMethod::new(&name, signature, is_class_method);
188 interface.add_method(method);
189 }
190 CXCursor_TemplateTypeParameter => {
191 let name = c.spelling();
192 interface.template_names.push(name);
193 }
194 CXCursor_ObjCSuperClassRef => {
195 let item = Item::from_ty_or_ref(c.cur_type(), c, None, ctx);
196 interface.parent_class = Some(item.into());
197 }
198 _ => {}
199 }
200 CXChildVisit_Continue
201 });
202 Some(interface)
203 }
204
205 fn add_method(&mut self, method: ObjCMethod) {
206 if method.is_class_method {
207 self.class_methods.push(method);
208 } else {
209 self.methods.push(method);
210 }
211 }
212}
213
214impl ObjCMethod {
215 fn new(
216 name: &str,
217 signature: FunctionSig,
218 is_class_method: bool,
219 ) -> ObjCMethod {
220 let split_name: Vec<&str> = name.split(':').collect();
221
222 let rust_name = split_name.join("_");
223
224 ObjCMethod {
225 name: name.to_owned(),
226 rust_name,
227 signature,
228 is_class_method,
229 }
230 }
231
232 /// The original method selector name
233 /// like, dataWithBytes:length:
234 pub fn name(&self) -> &str {
235 self.name.as_ref()
236 }
237
238 /// Method name as converted to rust
239 /// like, dataWithBytes_length_
240 pub fn rust_name(&self) -> &str {
241 self.rust_name.as_ref()
242 }
243
244 /// Returns the methods signature as FunctionSig
245 pub fn signature(&self) -> &FunctionSig {
246 &self.signature
247 }
248
249 /// Is this a class method?
250 pub fn is_class_method(&self) -> bool {
251 self.is_class_method
252 }
253
254 /// Formats the method call
255 pub fn format_method_call(&self, args: &[TokenStream]) -> TokenStream {
256 let split_name: Vec<Option<Ident>> = self
257 .name
258 .split(':')
259 .map(|name| {
260 if name.is_empty() {
261 None
262 } else {
263 // Try to parse the current name as an identifier. This might fail if the
264 // name is a keyword so we try to prepend "r#" to it and parse again. If
265 // this also fails, we panic with the first error.
266 Some(
267 syn::parse_str::<Ident>(name)
268 .or_else(|err| {
269 syn::parse_str::<Ident>(&format!("r#{}", name))
270 .map_err(|_| err)
271 })
272 .expect("Invalid identifier"),
273 )
274 }
275 })
276 .collect();
277
278 // No arguments
279 if args.is_empty() && split_name.len() == 1 {
280 let name = &split_name[0];
281 return quote! {
282 #name
283 };
284 }
285
286 // Check right amount of arguments
287 assert!(
288 args.len() == split_name.len() - 1,
289 "Incorrect method name or arguments for objc method, {:?} vs {:?}",
290 args,
291 split_name
292 );
293
294 // Get arguments without type signatures to pass to `msg_send!`
295 let mut args_without_types = vec![];
296 for arg in args.iter() {
297 let arg = arg.to_string();
298 let name_and_sig: Vec<&str> = arg.split(' ').collect();
299 let name = name_and_sig[0];
300 args_without_types.push(Ident::new(name, Span::call_site()))
301 }
302
303 let args = split_name.into_iter().zip(args_without_types).map(
304 |(arg, arg_val)| {
305 if let Some(arg) = arg {
306 quote! { #arg: #arg_val }
307 } else {
308 quote! { #arg_val: #arg_val }
309 }
310 },
311 );
312
313 quote! {
314 #( #args )*
315 }
316 }
317}
318
319impl Trace for ObjCInterface {
320 type Extra = ();
321
322 fn trace<T>(&self, context: &BindgenContext, tracer: &mut T, _: &())
323 where
324 T: Tracer,
325 {
326 for method: &ObjCMethod in &self.methods {
327 method.signature.trace(context, tracer, &());
328 }
329
330 for class_method: &ObjCMethod in &self.class_methods {
331 class_method.signature.trace(context, tracer, &());
332 }
333
334 for protocol: &ItemId in &self.conforms_to {
335 tracer.visit(*protocol);
336 }
337 }
338}
339