Skip to main content

objc2/macros/
extern_protocol.rs

1/// Create a new trait to represent a protocol.
2///
3/// This is similar to a `@protocol` declaration in Objective-C.
4///
5/// See [Protocols - The Objective-C Programming Language][protocols] and
6/// [Working with Protocols - Programming with Objective-C][working-with] for
7/// general information about protocols in Objective-C.
8///
9/// This macro will create an `unsafe` trait with methods that provide access
10/// to the functionality exposed by the protocol.
11///
12/// Conforming to the protocol can be done in two ways:
13/// - For external classes, use the [`extern_conformance!`] macro.
14/// - For custom classes created with the [`define_class!`] macro, implement
15///   the trait inside the macro.
16///
17/// Objective-C has a smart feature where you can write `id<MyProtocol>`, and
18/// then work with the protocol as-if it was an object; this is very similar
19/// to `dyn` traits in Rust, and we implement it in a similar way, see
20/// [`ProtocolObject`] for details.
21///
22/// [protocols]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html
23/// [working-with]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html
24/// [`extern_conformance!`]: crate::extern_conformance
25/// [`ProtocolObject`]: crate::runtime::ProtocolObject
26///
27///
28/// # Specification
29///
30/// The syntax is similar enough to Rust syntax that if you invoke the macro
31/// with parentheses (as opposed to curly brackets), `rustfmt` will be able to
32/// format the contents.
33///
34/// This macro creates an `unsafe` trait with the specified methods. A default
35/// implementation of the method is generated based on the selector specified
36/// with `#[unsafe(method(a:selector:))]`. Similar to [`extern_methods!`], you
37/// can use the `#[unsafe(method_family = ...)]` attribute to override the
38/// inferred method family.
39///
40/// Other protocols that this protocol conforms to / inherits can be specified
41/// as supertraits.
42///
43/// The new trait `T` is automatically implemented for
44/// [`ProtocolObject<dyn T>`], which also means that [`ProtocolType`] is
45/// implemented for `dyn T`.
46///
47/// Finally, you can use the `#[optional]` attribute to mark optional methods.
48/// This currently doesn't have any effect, but probably will have one in the
49/// future when implementing protocols in [`define_class!`].
50///
51/// This macro otherwise shares similarities with [`extern_class!`] and
52/// [`extern_methods!`].
53///
54/// [`ProtocolObject<dyn T>`]: crate::runtime::ProtocolObject
55/// [`ProtocolType`]: crate::ProtocolType
56/// [`define_class!`]: crate::define_class
57/// [`extern_class!`]: crate::extern_class
58/// [`extern_methods!`]: crate::extern_methods
59///
60///
61/// # Safety
62///
63/// The following are required for using the macro itself:
64/// - The specified name must be an existing Objective-C protocol.
65/// - The protocol must actually inherit/conform to the protocols specified
66///   as supertraits.
67///
68/// Each method is annotated with `#[unsafe(method(...))]`, where you are
69/// responsible for ensuring that the declaration is correct.
70///
71/// While the following are required when implementing the `unsafe` trait for
72/// a new type:
73/// - The type must represent an object that implements the protocol.
74///
75///
76/// # Examples
77///
78/// Create a trait to represent the `NSItemProviderWriting` protocol (in
79/// practice, you would import this from `objc2-foundation`, this is just for
80/// demonstration purposes).
81///
82/// ```
83/// use std::ffi::c_void;
84/// use objc2::ffi::NSInteger;
85/// use objc2::rc::Retained;
86/// use objc2::runtime::{NSObject, NSObjectProtocol};
87/// use objc2::extern_protocol;
88/// # type NSArray<T> = T;
89/// # type NSString = NSObject;
90/// # type NSProgress = NSObject;
91/// # type NSItemProviderRepresentationVisibility = NSInteger;
92/// # #[cfg(defined_in_foundation)]
93/// use objc2_foundation::{NSArray, NSString, NSProgress, NSItemProviderRepresentationVisibility};
94///
95/// extern_protocol!(
96///     /// This comment will appear on the trait as expected.
97///     //
98///     // We could have named the trait something else on the Rust-side, and
99///     // then used this to keep it correct from Objective-C.
100///     // #[name = "NSItemProviderWriting"]
101///     //
102///     // SAFETY:
103///     // - The name is correct.
104///     // - The protocol does inherit from `NSObjectProtocol`.
105///     pub unsafe trait NSItemProviderWriting: NSObjectProtocol {
106///         //                                  ^^^^^^^^^^^^^^^^
107///         // This protocol inherits from the `NSObject` protocol
108///
109///         // This method we mark as `unsafe`, since we aren't using the
110///         // correct type for the completion handler.
111///         #[unsafe(method(loadDataWithTypeIdentifier:forItemProviderCompletionHandler:))]
112///         unsafe fn loadData(
113///             &self,
114///             type_identifier: &NSString,
115///             completion_handler: *mut c_void,
116///         ) -> Option<Retained<NSProgress>>;
117///
118///         // SAFETY: The method is correctly specified.
119///         #[unsafe(method(writableTypeIdentifiersForItemProvider))]
120///         fn writableTypeIdentifiersForItemProvider_class()
121///             -> Retained<NSArray<NSString>>;
122///
123///         // The rest of these are optional, which means that a user of
124///         // `define_class!` would not need to implement them.
125///
126///         // SAFETY: The method is correctly specified.
127///         #[unsafe(method(writableTypeIdentifiersForItemProvider))]
128///         #[optional]
129///         fn writableTypeIdentifiersForItemProvider(&self)
130///             -> Retained<NSArray<NSString>>;
131///
132///         // SAFETY: The method is correctly specified.
133///         #[unsafe(method(itemProviderVisibilityForRepresentationWithTypeIdentifier:))]
134///         #[optional]
135///         fn itemProviderVisibilityForRepresentation_class(
136///             type_identifier: &NSString,
137///         ) -> NSItemProviderRepresentationVisibility;
138///
139///         // SAFETY: The method is correctly specified.
140///         #[unsafe(method(itemProviderVisibilityForRepresentationWithTypeIdentifier:))]
141///         #[optional]
142///         fn itemProviderVisibilityForRepresentation(
143///             &self,
144///             type_identifier: &NSString,
145///         ) -> NSItemProviderRepresentationVisibility;
146///     }
147/// );
148///
149/// // Types can now implement `NSItemProviderWriting`, and use the methods
150/// // from it as we specified.
151/// ```
152///
153/// See the source code of `objc2-foundation` for many more examples.
154#[doc(alias = "@protocol")]
155#[macro_export]
156macro_rules! extern_protocol {
157    (
158        // The special #[name = $name:literal] attribute is supported here.
159        $(#[$($attrs:tt)*])*
160        $v:vis unsafe trait $protocol:ident $(: $conforms_to:ident $(+ $conforms_to_rest:ident)*)? {
161            $($methods:tt)*
162        }
163    ) => {
164        $crate::__extract_struct_attributes! {
165            ($(#[$($attrs)*])*)
166
167            ($crate::__inner_extern_protocol)
168            ($protocol)
169            ($v unsafe trait $protocol $(: $conforms_to $(+ $conforms_to_rest)*)? {
170                $crate::__extern_protocol_rewrite_methods! {
171                    $($methods)*
172                }
173            })
174        }
175    };
176}
177
178#[doc(hidden)]
179#[macro_export]
180macro_rules! __inner_extern_protocol {
181    (
182        ($protocol:ident)
183        ($protocol_definition:item)
184
185        ($($superclasses:tt)*)
186        ($($thread_kind:tt)*)
187        ($($name:tt)*)
188        ($($ivars:tt)*)
189        ($($derives:tt)*)
190        ($($attr_protocol:tt)*)
191        ($($attr_impl:tt)*)
192    ) => {
193        $($attr_protocol)*
194        $protocol_definition
195
196        $($attr_impl)*
197        unsafe impl<T> $protocol for $crate::runtime::ProtocolObject<T>
198        where
199            T: ?$crate::__macro_helpers::Sized + $protocol
200        {}
201
202        // SAFETY: The specified name is ensured by caller to be a protocol,
203        // and is correctly defined.
204        $($attr_impl)*
205        unsafe impl $crate::ProtocolType for dyn $protocol {
206            const NAME: &'static $crate::__macro_helpers::str = $crate::__fallback_if_not_set! {
207                ($($name)*)
208                ($crate::__macro_helpers::stringify!($protocol))
209            };
210            const __INNER: () = ();
211        }
212
213        // SAFETY: Anything that implements the protocol is valid to convert
214        // to `ProtocolObject<dyn [PROTO]>`.
215        $($attr_impl)*
216        unsafe impl<T> $crate::runtime::ImplementedBy<T> for dyn $protocol
217        where
218            T: ?$crate::__macro_helpers::Sized + $crate::Message + $protocol
219        {
220            const __INNER: () = ();
221        }
222
223        // TODO: Should we also implement `ImplementedBy` for `Send + Sync`
224        // types, as is done for `NSObjectProtocol`?
225
226        $crate::__extern_protocol_check_no_super!($($superclasses)*);
227
228        $crate::__extern_protocol_check_no_thread_kind!($($thread_kind)*);
229
230        $crate::__extern_protocol_check_no_ivars!($($ivars)*);
231
232        $crate::__extern_protocol_check_no_derives!($($derives)*);
233    };
234}
235
236#[doc(hidden)]
237#[macro_export]
238macro_rules! __extern_protocol_check_no_super {
239    () => {};
240    ($($ivars:tt)*) => {
241        $crate::__macro_helpers::compile_error!("#[super] is not supported in extern_protocol!");
242    };
243}
244
245#[doc(hidden)]
246#[macro_export]
247macro_rules! __extern_protocol_check_no_thread_kind {
248    () => {};
249    ($($ivars:tt)*) => {
250        $crate::__macro_helpers::compile_error!(
251            "#[thread_kind = ...] is not supported in extern_protocol!. Add MainThreadOnly or AnyThread bound instead"
252        );
253    };
254}
255
256#[doc(hidden)]
257#[macro_export]
258macro_rules! __extern_protocol_check_no_ivars {
259    () => {};
260    ($($ivars:tt)*) => {
261        $crate::__macro_helpers::compile_error!("#[ivars] is not supported in extern_protocol!");
262    };
263}
264
265#[doc(hidden)]
266#[macro_export]
267macro_rules! __extern_protocol_check_no_derives {
268    () => {};
269    ($($ivars:tt)*) => {
270        $crate::__macro_helpers::compile_error!(
271            "#[derive(...)] is not supported in extern_protocol!"
272        );
273    };
274}
275
276/// tt-munch each protocol method.
277#[doc(hidden)]
278#[macro_export]
279macro_rules! __extern_protocol_rewrite_methods {
280    // Base case
281    {} => {};
282
283    // Unsafe variant
284    {
285        $(#[$($m:tt)*])*
286        $v:vis unsafe fn $name:ident($($params:tt)*) $(-> $ret:ty)?
287        // TODO: Handle where bounds better
288        $(where $($where:ty : $bound:path),+ $(,)?)?;
289
290        $($rest:tt)*
291    } => {
292        $crate::__rewrite_self_param! {
293            ($($params)*)
294
295            ($crate::__extract_method_attributes)
296            ($(#[$($m)*])*)
297
298            ($crate::__extern_protocol_method_out)
299            ($v unsafe fn $name($($params)*) $(-> $ret)?)
300            ($($($where : $bound ,)+)?)
301        }
302
303        $crate::__extern_protocol_rewrite_methods! {
304            $($rest)*
305        }
306    };
307
308    // Safe variant
309    {
310        $(#[$($m:tt)*])*
311        $v:vis fn $name:ident($($params:tt)*) $(-> $ret:ty)?
312        // TODO: Handle where bounds better
313        $(where $($where:ty : $bound:path),+ $(,)?)?;
314
315        $($rest:tt)*
316    } => {
317        $crate::__rewrite_self_param! {
318            ($($params)*)
319
320            ($crate::__extract_method_attributes)
321            ($(#[$($m)*])*)
322
323            ($crate::__extern_protocol_method_out)
324            ($v fn $name($($params)*) $(-> $ret)?)
325            ($($($where : $bound ,)+)?)
326        }
327
328        $crate::__extern_protocol_rewrite_methods! {
329            $($rest)*
330        }
331    };
332}
333
334#[doc(hidden)]
335#[macro_export]
336macro_rules! __extern_protocol_method_out {
337    // Instance method
338    {
339        ($($function_start:tt)*)
340        ($($where:ty : $bound:path ,)*)
341
342        (add_method)
343        ($receiver:expr)
344        ($__receiver_ty:ty)
345        ($($__params_prefix:tt)*)
346        ($($params_rest:tt)*)
347
348        ($method_or_method_id:ident($($sel:tt)*))
349        ($($method_family:tt)*)
350        ($($optional:tt)*)
351        ($($attr_method:tt)*)
352        ($($attr_use:tt)*)
353    } => {
354        $($attr_method)*
355        $($function_start)*
356        where
357            Self: $crate::__macro_helpers::Sized + $crate::Message
358            $(, $where : $bound)*
359        {
360            $crate::__extern_methods_method_id_deprecated!($method_or_method_id($($sel)*));
361
362            #[allow(unused_unsafe)]
363            unsafe {
364                $crate::__method_msg_send! {
365                    ($receiver)
366                    ($($sel)*)
367                    ($($params_rest)*)
368
369                    ()
370                    ()
371                    ($($method_family)*)
372                }
373            }
374        }
375    };
376
377    // Class method
378    {
379        ($($function_start:tt)*)
380        ($($where:ty : $bound:path ,)*)
381
382        (add_class_method)
383        ($receiver:expr)
384        ($__receiver_ty:ty)
385        ($($__params_prefix:tt)*)
386        ($($params_rest:tt)*)
387
388        ($method_or_method_id:ident($($sel:tt)*))
389        ($($method_family:tt)*)
390        ($($optional:tt)*)
391        ($($attr_method:tt)*)
392        ($($attr_use:tt)*)
393    } => {
394        $($attr_method)*
395        $($function_start)*
396        where
397            Self: $crate::__macro_helpers::Sized + $crate::ClassType
398            $(, $where : $bound)*
399        {
400            $crate::__extern_methods_method_id_deprecated!($method_or_method_id($($sel)*));
401
402            #[allow(unused_unsafe)]
403            unsafe {
404                $crate::__method_msg_send! {
405                    ($receiver)
406                    ($($sel)*)
407                    ($($params_rest)*)
408
409                    ()
410                    ()
411                    ($($method_family)*)
412                }
413            }
414        }
415    };
416}
417
418#[doc(hidden)]
419#[macro_export]
420macro_rules! __extern_protocol_method_id_deprecated {
421    (method($($sel:tt)*)) => {};
422    (method_id($($sel:tt)*)) => {{
423        #[deprecated = $crate::__macro_helpers::concat!(
424            "using #[unsafe(method_id(",
425            $crate::__macro_helpers::stringify!($($sel)*),
426            "))] inside extern_protocol! is deprecated.\nUse #[unsafe(method(",
427            $crate::__macro_helpers::stringify!($($sel)*),
428            "))] instead",
429        )]
430        #[inline]
431        fn method_id() {}
432        method_id();
433    }};
434}
435
436#[cfg(test)]
437mod tests {
438    use crate::{extern_protocol, ProtocolType};
439
440    #[test]
441    fn explicit_name() {
442        extern_protocol!(
443            #[allow(clippy::missing_safety_doc)]
444            #[name = "NSObject"]
445            unsafe trait Foo {}
446        );
447
448        let proto = <dyn Foo>::protocol().unwrap();
449        assert_eq!(proto.name().to_str().unwrap(), "NSObject");
450        assert_eq!(<dyn Foo>::NAME, "NSObject");
451    }
452}