Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving how we map protocols #291

Closed
madsmtm opened this issue Nov 16, 2022 · 5 comments · Fixed by #293
Closed

Improving how we map protocols #291

madsmtm opened this issue Nov 16, 2022 · 5 comments · Fixed by #293
Labels
A-framework Affects the framework crates and the translator for them A-objc2 Affects the `objc2`, `objc2-exception-helper` and/or `objc2-encode` crates enhancement New feature or request
Milestone

Comments

@madsmtm
Copy link
Owner

madsmtm commented Nov 16, 2022

Continuing on the work done in #250.

Currently

A protocol is mapped to a struct that impls Message, which allows using it in Id.
This allows similar patterns as id<ProtocolName> would do in Objective-C, but has a lot of tradeoffs, including:

  1. Calling a method on a protocol is more cumbersome than doing it on a normal struct.

    use icrate::Foundation::{NSLock, NSLocking};
    
    let obj = NSLock::new();
    
    // Not possible to call `lock` automatically, we have to convert it to a protocol object first
    // obj.lock();
    
    let proto: &NSLocking = obj.as_protocol();
    proto.lock();
    proto.unlock();
  2. Protocol class methods are impossible to do

    use icrate::Foundation::NSString;
    let res: bool = unsafe { msg_send![NSString::class(), supportsSecureCoding] };
  3. Calling methods on protocols that has multiple parent protocols is cumbersome:

    use icrate::AppKit::{NSTextCheckingClient, NSTextInputClient, NSTextInputTraits};
    let proto: &NSTextCheckingClient = ...; // Inherits NSTextInputClient and NSTextInputTraits
    proto.addAnnotations_range(...);
    
    // proto.hasMarkedText();
    let parent1: &NSTextInputClient = proto.as_protocol();
    parent1.hasMarkedText();
    
    // proto.autocorrectionType();
    let parent2: &NSTextInputTraits = proto.as_protocol();
    parent2.autocorrectionType();

Ideally

Ideally the constraints outlined above would be gone

  1. use icrate::Foundation::{NSLock, NSLocking};
    
    let obj = NSLock::new();
    
    obj.lock(); // Calls -[NSLocking lock]
    
    // Still possible
    let proto: &NSLocking = obj.as_protocol();
    
    proto.unlock();
  2. use icrate::Foundation::{NSString, NSSecureCoding};
    let res = NSString::supportsSecureCoding();
  3. use icrate::AppKit::{NSTextCheckingClient, NSTextInputClient, NSTextInputTraits};
    let proto: &NSTextCheckingClient = ...;
    proto.addAnnotations_range(...);
    proto.hasMarkedText();
    proto.autocorrectionType();

Idea

Maybe we could use something like Newtype<dyn MyProtocol> in place of &MyProtocol (since we can't override the behaviour of &dyn MyProtocol)? And do specialized stuff in Id to handle Id<dyn MyProtocol>?

@madsmtm
Copy link
Owner Author

madsmtm commented Nov 16, 2022

Further idea generation:

trait ProtocolType {}

// TODO: Better name
struct Helper<P: ProtocolType> {
    __inner: Object,
    p: PhantomData<P>,
}

trait ConformsTo<P: ProtocolType> {
    fn as_protocol(&self) -> &Helper<P>;
    fn as_protocol_mut(&mut self) -> &mut Helper<P>;
}

// NSLocking.rs

trait NSLocking: Message {
    unsafe fn lock(&self) {
        msg_send![self, lock]
    }

    unsafe fn unlock(&self) {
        msg_send![self, unlock]
    }
}

impl ProtocolType for dyn NSLocking {}
impl NSLocking for Helper<dyn NSLocking> {}

// NSLock.rs

declare_class!(NSLock ...);

impl NSLocking for NSLock {}
impl ConformsTo<dyn NSLocking> for NSLock {}

// NSSecureCoding.rs

trait NSSecureCoding: Message {
    fn supportsSecureCoding_class() -> bool
    where
        Self: ClassType
    {
        msg_send![Self::class(), supportsSecureCoding]
    }
}

impl ProtocolType for dyn NSSecureCoding {}
impl NSSecureCoding for Helper<dyn NSSecureCoding> {}

// NSTextCheckingClient.rs

trait NSTextCheckingClient: NSTextInputClient + NSTextInputTraits { ... }

Usage:

  1. use icrate::Foundation::{NSLock, NSLocking};
    
    let obj = NSLock::new();
    
    obj.lock(); // Calls NSLocking::lock(&obj)
    
    let proto: &Helper<dyn NSLocking> = obj.as_protocol();
    
    // `&Helper<dyn NSLocking>` is ABI-compatible with `&Object`
    
    proto.unlock(); // Calls NSLocking::unlock(&proto)
  2. use icrate::Foundation::{NSString, NSSecureCoding};
    let res = NSString::supportsSecureCoding();
    // Intentionally not possible:
    // let res = <Helper<dyn NSSecureCoding>>::supportsSecureCoding();
  3. use icrate::AppKit::{NSTextCheckingClient, NSTextInputClient, NSTextInputTraits};
    let proto: &Helper<dyn NSTextCheckingClient> = ...;
    proto.addAnnotations_range(...);
    proto.hasMarkedText();
    proto.autocorrectionType();

@madsmtm madsmtm added A-objc2 Affects the `objc2`, `objc2-exception-helper` and/or `objc2-encode` crates enhancement New feature or request labels Nov 23, 2022
@madsmtm madsmtm added the A-framework Affects the framework crates and the translator for them label Dec 23, 2022
@ericmarkmartin
Copy link

What's the current state of extern_protocol? Along the lines of the doc comment above extern_protocol, I've written

extern_protocol!(
    struct ASAuthorizationControllerDelegateObject;

    unsafe impl ProtocolType for ASAuthorizationControllerDelegateObject {
        const NAME: &'static str = "ASAuthorizationControllerDelegate";
        
        #[method(attestationController:controller:digCompelteWithAuthorization:)]
        unsafe fn authorization_controller_did_complete_with_authorization(
            &self,
            _controller: &ASAuthorizationController,
            authorization: &ASAuthorization,
        );
    }
);

and I'm getting

error[E0308]: mismatched types
  --> webauthn-authenticator-rs/src/macos.rs:26:1
   |
26 | / extern_protocol!(
27 | |     struct ASAuthoriationControllerDelegateObject;
28 | |
29 | |     unsafe impl ProtocolType for ASAuthorizationControllerDelegateObject {
...  |
38 | |     }
39 | | );
   | | ^
   | | |
   | |_expected `()`, found enum `Result`
   |   expected `()` because of default return type
   |
   = note: expected unit type `()`
                   found enum `Result<(), icrate::objc2::rc::Id<_, Shared>>`
   = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `extern_protocol` (in Nightly builds, run with -Z macro-backtrace for more info)

@madsmtm
Copy link
Owner Author

madsmtm commented Dec 27, 2022

The issue seems to be that you've specified the wrong selector? attestationController:controller:digCompelteWithAuthorization: takes three parameters, but your function only seems to take 2?

See also how it's used in icrate.

@ericmarkmartin
Copy link

ericmarkmartin commented Dec 28, 2022

Oh sorry you're totally right. That'll teach meto write this stuff that late at night...

@madsmtm
Copy link
Owner Author

madsmtm commented Jan 5, 2023

#321 should fix the error message ;)

@madsmtm madsmtm added this to the objc2 v0.3 milestone Jan 18, 2023
@madsmtm madsmtm linked a pull request Jan 27, 2023 that will close this issue
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-framework Affects the framework crates and the translator for them A-objc2 Affects the `objc2`, `objc2-exception-helper` and/or `objc2-encode` crates enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants