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}