Skip to main content

objc2/macros/
extern_methods.rs

1/// Define methods on an external class.
2///
3/// This is a convenience macro to generate associated functions and methods
4/// that delegate to [`msg_send!`].
5///
6/// [`msg_send!`]: crate::msg_send
7///
8///
9/// # Specification
10///
11/// Within the `impl` block you can define two types of functions without
12/// bodies; ["associated functions"] and ["methods"]. These are then mapped to
13/// the Objective-C equivalents "class methods" and "instance methods", and an
14/// appropriate body is created for you. In particular, if you use `self` or
15/// the special name `this` (or `_this`), your method will assumed to be an
16/// instance method, and if you don't it will be assumed to be a class method.
17///
18/// If you specify a function/method with a body, the macro will output it
19/// unchanged.
20///
21/// The name of the function will be used for the resulting function that the
22/// user will use to access the functionality, but is otherwise not used by
23/// the macro.
24///
25/// If you use `objc2::MainThreadMarker` as a parameter type, the macro will
26/// ignore it, allowing you to neatly specify "this method must be run on the
27/// main thread". Note that due to type-system limitations, this is currently
28/// a textual match on `MainThreadMarker`; so you must use that exact
29/// identifier.
30///
31/// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods
32/// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods
33///
34///
35/// ## Attributes
36///
37/// You can add most normal attributes to the methods, including
38/// `#[cfg(...)]`, `#[allow(...)]`, `#[deprecated = ...]` and doc comments.
39///
40/// Exceptions and special attributes are noted below.
41///
42///
43/// ### `#[unsafe(method(...))]` (required)
44///
45/// Specify the desired selector using this attribute.
46///
47/// If the selector ends with "_", as in `#[unsafe(method(my:error:_))]`, the
48/// method is assumed to take an implicit `NSError**` parameter, which is
49/// automatically converted to a [`Result`]. See the error section in
50/// [`msg_send!`] for details.
51///
52///
53/// ### `#[unsafe(method_family = ...)]` (optional)
54///
55/// The Cocoa memory management convention is figured out automatically based
56/// on the name of the selector, but it can be overwritten with this `unsafe`
57/// attribute.
58///
59/// This is commonly done in framework crates to improve compile-time
60/// performance, as the logic to determine the family automatically can be
61/// quite taxing at scale. That said, you should rarely need to use this
62/// yourself.
63///
64/// The valid family names are:
65/// - `alloc`.
66/// - `new`.
67/// - `init`.
68/// - `copy`.
69/// - `mutableCopy`.
70///
71/// As well as the special `none` family that opts-out of being in a family.
72///
73/// This corresponds to the `__attribute__((objc_method_family(family)))` C
74/// attribute, see [Clang's documentation][clang-method-families].
75///
76/// [clang-method-families]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#method-families
77///
78///
79/// #### Safety
80///
81/// You must ensure that the specified method family is correct.
82///
83///
84/// ### `#[cfg_attr(..., ...)]`
85///
86/// This is only supported for attributes that apply to the method itself
87/// (i.e. not supported for attributes that apply to any of the custom
88/// attributes, due to implementation difficulty).
89///
90///
91/// # Safety
92///
93/// You must ensure that any methods you declare with the
94/// `#[unsafe(method(...))]` attribute upholds the safety guarantees described
95/// in the [`msg_send!`] macro, _or_ are marked `unsafe`.
96///
97///
98/// # Examples
99///
100/// Let's create a quick custom class:
101///
102/// ```
103/// use objc2::encode::{Encode, Encoding};
104/// use objc2::ffi::NSUInteger;
105/// use objc2::rc::{Allocated, Retained};
106/// use objc2::runtime::NSObject;
107/// use objc2::{define_class, extern_methods};
108///
109/// // Shim
110/// type NSError = NSObject;
111///
112/// define_class!(
113///     // SAFETY:
114///     // - The superclass NSObject does not have any subclassing requirements.
115///     // - `MyObject` does not implement `Drop`.
116///     #[unsafe(super(NSObject))]
117///     pub struct MyObject;
118///
119///     impl MyObject {
120///         // ... Assume we've implemented all the methods used below
121///     }
122/// );
123///
124/// /// Creation methods.
125/// impl MyObject {
126///     extern_methods!(
127///         // SAFETY: The method is correctly specified.
128///         #[unsafe(method(new))]
129///         pub fn new() -> Retained<Self>;
130///
131///         // SAFETY: The method is correctly specified.
132///         #[unsafe(method(initWithVal:))]
133///         // arbitrary self types are not stable, but we can work around it
134///         // with the special name `this`.
135///         pub fn init(this: Allocated<Self>, val: usize) -> Retained<Self>;
136///     );
137/// }
138///
139/// /// Instance accessor methods.
140/// impl MyObject {
141///     extern_methods!(
142///         // SAFETY: The method is correctly specified.
143///         #[unsafe(method(foo))]
144///         pub fn foo(&self) -> NSUInteger;
145///
146///         // SAFETY: The method is correctly specified.
147///         #[unsafe(method(fooObject))]
148///         pub fn foo_object(&self) -> Retained<NSObject>;
149///
150///         // SAFETY: The method is correctly specified.
151///         #[unsafe(method(withError:_))]
152///         // Since the selector specifies "_", the return type is assumed to
153///         // be `Result`.
154///         pub fn with_error(&self) -> Result<(), Retained<NSError>>;
155///     );
156/// }
157/// ```
158///
159/// The `extern_methods!` then expands to (roughly):
160///
161/// ```
162/// # use objc2::encode::{Encode, Encoding};
163/// # use objc2::ffi::NSUInteger;
164/// # use objc2::rc::{Allocated, Retained};
165/// # use objc2::runtime::NSObject;
166/// # use objc2::{define_class, extern_methods, ClassType};
167/// #
168/// # // Shim
169/// # type NSError = NSObject;
170/// #
171/// # define_class!(
172/// #     #[unsafe(super(NSObject))]
173/// #     pub struct MyObject;
174/// #
175/// #     impl MyObject {
176/// #         // ... Assume we've implemented all the methods used below
177/// #     }
178/// # );
179/// #
180/// use objc2::msg_send;
181///
182/// /// Creation methods.
183/// impl MyObject {
184///     pub fn new() -> Retained<Self> {
185///         unsafe { msg_send![Self::class(), new] }
186///     }
187///
188///     pub fn init(this: Allocated<Self>, val: usize) -> Retained<Self> {
189///         unsafe { msg_send![this, initWithVal: val] }
190///     }
191/// }
192///
193/// /// Instance accessor methods.
194/// impl MyObject {
195///     pub fn foo(&self) -> NSUInteger {
196///         unsafe { msg_send![self, foo] }
197///     }
198///
199///     pub fn foo_object(&self) -> Retained<NSObject> {
200///         unsafe { msg_send![self, fooObject] }
201///     }
202///
203///     // Since the selector specifies one more argument than we
204///     // have, the return type is assumed to be `Result`.
205///     pub fn with_error(&self) -> Result<(), Retained<NSError>> {
206///         unsafe { msg_send![self, withError: _] }
207///     }
208/// }
209/// ```
210///
211/// See the source code of `objc2-foundation` for many more examples.
212#[macro_export]
213macro_rules! extern_methods {
214    (
215        // Base case of the tt-muncher.
216    ) => {};
217
218    (
219        // Unsafe method.
220        //
221        // Special attributes:
222        // #[unsafe(method($($selector:tt)+))]
223        // #[unsafe(method_family = $family:ident)]
224        $(#[$($m:tt)*])*
225        $v:vis unsafe fn $fn_name:ident($($params:tt)*) $(-> $ret:ty)?
226        // Optionally, a single `where` bound.
227        // TODO: Handle this better.
228        $(where $($where:ty : $bound:path),+ $(,)?)?;
229
230        $($rest:tt)*
231    ) => {
232        $crate::__rewrite_self_param! {
233            ($($params)*)
234
235            ($crate::__extract_method_attributes)
236            ($(#[$($m)*])*)
237
238            ($crate::__extern_methods_method_out)
239            ($v unsafe fn $fn_name($($params)*) $(-> $ret)?)
240            ($($($where : $bound ,)+)?)
241        }
242
243        $crate::extern_methods!($($rest)*);
244    };
245
246    (
247        // Safe method.
248        //
249        // Special attributes:
250        // #[unsafe(method($($selector:tt)+))]
251        // #[unsafe(method_family = $family:ident)]
252        $(#[$($m:tt)*])*
253        $v:vis fn $fn_name:ident($($params:tt)*) $(-> $ret:ty)?
254        // Optionally, a single `where` bound.
255        // TODO: Handle this better.
256        $(where $($where:ty : $bound:path),+ $(,)?)?;
257
258        $($rest:tt)*
259    ) => {
260        $crate::__rewrite_self_param! {
261            ($($params)*)
262
263            ($crate::__extract_method_attributes)
264            ($(#[$($m)*])*)
265
266            ($crate::__extern_methods_method_out)
267            ($v fn $fn_name($($params)*) $(-> $ret)?)
268            ($($($where : $bound ,)+)?)
269        }
270
271        $crate::extern_methods!($($rest)*);
272    };
273
274    (
275        // Deprecated syntax.
276        $(#[$m:meta])*
277        unsafe impl $type:ty {
278            $($methods:tt)*
279        }
280
281        $($rest:tt)*
282    ) => {
283        const _: () = $crate::__macro_helpers::extern_methods_unsafe_impl();
284
285        $(#[$m])*
286        impl<$($t $(: $b $(+ $rest)*)?),*> $type {
287            $crate::extern_methods! {
288                $($methods)*
289            }
290        }
291
292        $crate::extern_methods!($($rest)*);
293    };
294}
295
296#[doc(hidden)]
297#[macro_export]
298macro_rules! __extern_methods_method_out {
299    {
300        ($($function_start:tt)*)
301        ($($where:ty : $bound:path ,)*)
302
303        ($__builder_method:ident)
304        ($receiver:expr)
305        ($__receiver_ty:ty)
306        ($($__params_prefix:tt)*)
307        ($($params_rest:tt)*)
308
309        ($method_or_method_id:ident($($sel:tt)*))
310        ($($method_family:tt)*)
311        ($($optional:tt)*)
312        ($($attr_method:tt)*)
313        ($($attr_use:tt)*)
314    } => {
315        $($attr_method)*
316        $($function_start)*
317        where
318            $($where : $bound,)*
319        {
320            $crate::__extern_methods_method_id_deprecated!($method_or_method_id($($sel)*));
321            $crate::__extern_methods_no_optional!($($optional)*);
322
323            #[allow(unused_unsafe)]
324            unsafe {
325                $crate::__method_msg_send! {
326                    ($receiver)
327                    ($($sel)*)
328                    ($($params_rest)*)
329
330                    ()
331                    ()
332                    ($($method_family)*)
333                }
334            }
335        }
336    };
337}
338
339#[doc(hidden)]
340#[macro_export]
341macro_rules! __extern_methods_no_optional {
342    () => {};
343    (#[optional]) => {
344        $crate::__macro_helpers::compile_error!(
345            "`#[optional]` is only supported in `extern_protocol!`"
346        )
347    };
348}
349
350#[doc(hidden)]
351#[macro_export]
352macro_rules! __extern_methods_method_id_deprecated {
353    (method($($sel:tt)*)) => {};
354    (method_id($($sel:tt)*)) => {{
355        #[deprecated = $crate::__macro_helpers::concat!(
356            "using #[unsafe(method_id(",
357            $crate::__macro_helpers::stringify!($($sel)*),
358            "))] inside extern_methods! is deprecated.\nUse #[unsafe(method(",
359            $crate::__macro_helpers::stringify!($($sel)*),
360            "))] instead",
361        )]
362        #[inline]
363        fn method_id() {}
364        method_id();
365    }};
366}
367
368#[cfg(test)]
369mod tests {
370    use crate::extern_methods;
371    use crate::runtime::{NSObject, NSObjectProtocol};
372
373    #[test]
374    fn outside_impl_using_this() {
375        // The fact that this works outside `impl` is an implementation detail
376        // that will get resolved once we have arbitrary self types.
377        extern_methods!(
378            #[unsafe(method(hash))]
379            fn obj_hash(this: &NSObject) -> usize;
380        );
381
382        let obj = NSObject::new();
383        assert_eq!(obj_hash(&obj), obj.hash())
384    }
385}