-
Notifications
You must be signed in to change notification settings - Fork 49
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
Fix enums and typedefs in framework crates #310
Comments
I believe the implementation I've been working on in #425 does a reasonably good job now of handling enums at least. I generally tried to follow a similar approach for mapping things over to how the enums are translated for Swift. There are some caveats, for example due to the fact that we can't define an The macros automatically handle renaming by stripping variant prefixes if they match the enum name: #[objc(error_enum,
mod = sn_error,
domain = unsafe { SNErrorDomain }
)]
pub enum SNErrorCode {
SNErrorCodeUnknownError = 1,
SNErrorCodeOperationFailed,
SNErrorCodeInvalidFormat,
SNErrorCodeInvalidModel,
SNErrorCodeInvalidFile,
} This generates code like this fragment (along with a bunch of additional helper machinery): struct SNError {} // inherits from `NSError`
mod sn_error {
struct Codes(isize);
impl Codes {
const UnknownError: Self = Self(Cases::UnknownError as isize);
const OperationFailed: Self = Self(Cases::OperationFailed as isize);
const InvalidFormat: Self = Self(Cases::InvalidFormat as isize);
const InvalidModel: Self = Self(Cases::InvalidModel as isize);
const InvalidFile: Self = Self(Cases::InvalidFile as isize);
}
pub enum Cases {
UnknownError = 1,
OperationFailed,
InvalidFormat,
InvalidModel,
InvalidFile,
}
} Also, if you use the name The most sophisticated example is A couple things to note: You can (optionally) specify the name of the helper module with You can also (optionally) specify refinements for the Similar to Swift, getters are generated for the specified // `UserInfo<T>` is an alias for `<T as objc2::ErrorEnum>::UserInfo` (to avoid mentioning the helper module)
let UserInfo::<NSURLError> {
failing_url,
failing_url_string,
background_task_cancelled_reason,
network_unavailable_reason,
} = error.user_info();
let failing_url_string = error.failing_url_string(); The error declaration looks like this: #[objc(error_enum,
mod = ns_url_error,
domain = unsafe { NSURLErrorDomain },
// NOTE: Here we specify (typed) getters for the associated `userInfo` dict.
user_info = [
/// # SAFETY (type)
/// https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlerrorkey?language=objc
{ key = NSURLErrorFailingURLErrorKey, unsafe type = Id<NSURL> },
/// # SAFETY (type)
/// https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlstringerrorkey?language=objc
{ key = NSURLErrorFailingURLStringErrorKey, unsafe type = Id<NSString> },
/// # SAFETY (type)
/// https://developer.apple.com/documentation/foundation/nsurlerrorbackgroundtaskcancelledreasonkey?language=objc
{ key = NSURLErrorBackgroundTaskCancelledReasonKey, unsafe type = ns_url_error_reasons::BackgroundTaskCancelledReason },
/// # SAFETY (type)
/// https://developer.apple.com/documentation/foundation/nsurlerrornetworkunavailablereasonkey?language=objc'
///
/// # SAFETY (from)
/// 1. Casting dictionary `Object` to `NSNumber` is safe (type)
/// 2. `NSNumber` can safely be converted to `NetworkUnavailableReason`
///
/// # SAFETY (into)
/// `NetworkUnavailableReason` can safely be converted to `NSObject` via deref (through
/// converting to `NSNumber` first). This happens implicitly in the `&*arg` that
/// occurs during the call to insert the value in the dictionary.
{
key = NSURLErrorNetworkUnavailableReasonKey,
unsafe type = ns_url_error_reasons::NetworkUnavailableReason,
unsafe from = |n: Option<Id<NSNumber>>| n.map(TryInto::try_into).and_then(Result::ok),
unsafe into = std::convert::identity,
// NOTE: `from` and `into` aren't necessary in this case since they can be computed
// automatically but this is what they look like for demonstration purposes.
},
]
)]
pub enum NSURLError {
Unknown = -1,
Cancelled = -999,
BadURL = -1000,
TimedOut = -1001,
UnsupportedURL = -1002,
CannotFindHost = -1003,
CannotConnectToHost = -1004,
NetworkConnectionLost = -1005,
DNSLookupFailed = -1006,
HTTPTooManyRedirects = -1007,
ResourceUnavailable = -1008,
NotConnectedToInternet = -1009,
RedirectToNonExistentLocation = -1010,
BadServerResponse = -1011,
UserCancelledAuthentication = -1012,
UserAuthenticationRequired = -1013,
ZeroByteResource = -1014,
CannotDecodeRawData = -1015,
CannotDecodeContentData = -1016,
CannotParseResponse = -1017,
AppTransportSecurityRequiresSecureConnection = -1022,
FileDoesNotExist = -1100,
FileIsDirectory = -1101,
NoPermissionsToReadFile = -1102,
DataLengthExceedsMaximum = -1103,
FileOutsideSafeArea = -1104,
SecureConnectionFailed = -1200,
ServerCertificateHasBadDate = -1201,
ServerCertificateUntrusted = -1202,
ServerCertificateHasUnknownRoot = -1203,
ServerCertificateNotYetValid = -1204,
ClientCertificateRejected = -1205,
ClientCertificateRequired = -1206,
CannotLoadFromNetwork = -2000,
CannotCreateFile = -3000,
CannotOpenFile = -3001,
CannotCloseFile = -3002,
CannotWriteToFile = -3003,
CannotRemoveFile = -3004,
CannotMoveFile = -3005,
DownloadDecodingFailedMidStream = -3006,
DownloadDecodingFailedToComplete = -3007,
InternationalRoamingOff = -1018,
CallIsActive = -1019,
DataNotAllowed = -1020,
RequestBodyStreamExhausted = -1021,
BackgroundSessionRequiresSharedContainer = -995,
BackgroundSessionInUseByAnotherProcess = -996,
BackgroundSessionWasDisconnected = -997,
} which expands to the following: // Recursive expansion of objc! macro
// ===================================
#[cfg(all(
feature = "Foundation",
feature = "Foundation_NSError",
feature = "Foundation_NSString"
))]
objc2::declare_class!(
pub struct NSURLError {}
unsafe impl ClassType for NSURLError {
type Super = icrate::Foundation::NSError;
const NAME: &'static str = "NSURLError";
}
);
#[cfg(all(
feature = "Foundation",
feature = "Foundation_NSError",
feature = "Foundation_NSString"
))]
impl NSURLError {
#[inline]
pub fn domain() -> &'static icrate::Foundation::NSErrorDomain {
unsafe { NSURLErrorDomain }
}
#[inline]
pub fn code(&self) -> ns_url_error::Codes {
ns_url_error::Codes(self.as_super().code())
}
#[inline]
pub fn localized_description(&self) -> objc2::rc::Id<icrate::Foundation::NSString> {
self.as_super().localizedDescription()
}
#[inline]
pub fn user_info(&self) -> ns_url_error::UserInfo {
let mut user_info = ns_url_error::UserInfo::default();
user_info.failing_url = self.failing_url();
user_info.failing_url_string = self.failing_url_string();
user_info.background_task_cancelled_reason = self.background_task_cancelled_reason();
user_info.network_unavailable_reason = self.network_unavailable_reason();
user_info
}
#[inline]
pub fn new(code: ns_url_error::Codes) -> objc2::rc::Id<Self, objc2::rc::Shared> {
let code = code.0;
let domain = Self::domain();
let error = unsafe { icrate::Foundation::NSError::new(code, domain) };
unsafe { objc2::rc::Id::cast(error) }
}
#[cfg(all(
feature = "Foundation_NSDictionary",
feature = "Foundation_NSMutableDictionary"
))]
#[inline]
fn new_with_user_info(
code: ns_url_error::Codes,
user_info: ns_url_error::UserInfo,
) -> objc2::rc::Id<Self, objc2::rc::Shared> {
let domain = Self::domain();
let code = code.0;
let dict = Self::user_info_into_dictionary(user_info);
let error = unsafe {
icrate::Foundation::NSError::errorWithDomain_code_userInfo(domain, code, Some(&*dict))
};
unsafe { objc2::rc::Id::cast(error) }
}
#[doc(hidden)]
#[inline]
fn user_info_into_dictionary(
user_info: ns_url_error::UserInfo,
) -> objc2::rc::Id<
icrate::Foundation::NSMutableDictionary<
icrate::Foundation::NSErrorUserInfoKey,
objc2::runtime::Object,
>,
> {
let dict = icrate::Foundation::NSMutableDictionary::<
icrate::Foundation::NSErrorUserInfoKey,
objc2::runtime::Object,
>::new();
#[allow(unused_unsafe)]
if let Some(value) = (user_info.failing_url) {
unsafe { dict.setValue_forKey(Some(&*value), NSURLErrorFailingURLErrorKey) };
}
#[allow(unused_unsafe)]
if let Some(value) = (user_info.failing_url_string) {
unsafe { dict.setValue_forKey(Some(&*value), NSURLErrorFailingURLStringErrorKey) };
}
#[allow(unused_unsafe)]
if let Some(value) = (user_info.background_task_cancelled_reason) {
unsafe {
dict.setValue_forKey(Some(&*value), NSURLErrorBackgroundTaskCancelledReasonKey)
};
}
#[allow(unused_unsafe)]
if let Some(value) = (std::convert::identity)(user_info.network_unavailable_reason) {
unsafe { dict.setValue_forKey(Some(&*value), NSURLErrorNetworkUnavailableReasonKey) };
}
objc2::rc::Id::into_shared(dict)
}
#[doc = " # SAFETY (type)"]
#[doc = " https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlerrorkey?language=objc"]
fn failing_url(&self) -> Option<Id<NSURL>> {
let user_info = self.as_super().userInfo();
let value = unsafe { user_info.valueForKey(NSURLErrorFailingURLErrorKey) }
.map(|inner| unsafe { objc2::rc::Id::cast(inner) });
#[allow(unused_unsafe)]
(|n: Option<_>| n.map(TryInto::try_into).and_then(Result::ok))(value)
}
#[doc = " # SAFETY (type)"]
#[doc = " https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlstringerrorkey?language=objc"]
fn failing_url_string(&self) -> Option<Id<NSString>> {
let user_info = self.as_super().userInfo();
let value = unsafe { user_info.valueForKey(NSURLErrorFailingURLStringErrorKey) }
.map(|inner| unsafe { objc2::rc::Id::cast(inner) });
#[allow(unused_unsafe)]
(|n: Option<_>| n.map(TryInto::try_into).and_then(Result::ok))(value)
}
#[doc = " # SAFETY (type)"]
#[doc = " https://developer.apple.com/documentation/foundation/nsurlerrorbackgroundtaskcancelledreasonkey?language=objc"]
fn background_task_cancelled_reason(
&self,
) -> Option<ns_url_error_reasons::BackgroundTaskCancelledReason> {
let user_info = self.as_super().userInfo();
let value = unsafe { user_info.valueForKey(NSURLErrorBackgroundTaskCancelledReasonKey) }
.map(|inner| unsafe { objc2::rc::Id::cast(inner) });
#[allow(unused_unsafe)]
(|n: Option<_>| n.map(TryInto::try_into).and_then(Result::ok))(value)
}
#[doc = " # SAFETY (type)"]
#[doc = " https://developer.apple.com/documentation/foundation/nsurlerrornetworkunavailablereasonkey?language=objc\'"]
#[doc = " "]
#[doc = " # SAFETY (from)"]
#[doc = " 1. Casting dictionary `Object` to `NSNumber` is safe (type)"]
#[doc = " 2. `NSNumber` can safely be converted to `NetworkUnavailableReason`"]
#[doc = ""]
#[doc = " # SAFETY (into)"]
#[doc = " `NetworkUnavailableReason` can safely be converted to `NSObject` via deref (through"]
#[doc = " converting to `NSNumber` first). This happens implicitly in the `&*arg` that"]
#[doc = " occurs during the call to insert the value in the dictionary."]
fn network_unavailable_reason(&self) -> Option<ns_url_error_reasons::NetworkUnavailableReason> {
let user_info = self.as_super().userInfo();
let value = unsafe { user_info.valueForKey(NSURLErrorNetworkUnavailableReasonKey) }
.map(|inner| unsafe { objc2::rc::Id::cast(inner) });
#[allow(unused_unsafe)]
(|n: Option<Id<NSNumber>>| n.map(TryInto::try_into).and_then(Result::ok))(value)
}
}
#[cfg(all(
feature = "Foundation",
feature = "Foundation_NSError",
feature = "Foundation_NSString"
))]
impl objc2::ErrorEnum for NSURLError {
type Codes = ns_url_error::Codes;
type UserInfo = ns_url_error::UserInfo;
}
#[cfg(all(
feature = "Foundation",
feature = "Foundation_NSError",
feature = "Foundation_NSString"
))]
impl objc2::TypedEnum for NSURLError {
type Cases = ns_url_error::Cases;
}
#[allow(non_snake_case)]
pub mod ns_url_error {
#[derive(Eq, PartialEq)]
#[repr(transparent)]
pub struct Codes(pub(super) isize);
impl Codes {
#![allow(non_upper_case_globals)]
pub const Unknown: Self = Self(Cases::Unknown as isize);
pub const Cancelled: Self = Self(Cases::Cancelled as isize);
pub const BadURL: Self = Self(Cases::BadURL as isize);
pub const TimedOut: Self = Self(Cases::TimedOut as isize);
pub const UnsupportedURL: Self = Self(Cases::UnsupportedURL as isize);
pub const CannotFindHost: Self = Self(Cases::CannotFindHost as isize);
pub const CannotConnectToHost: Self = Self(Cases::CannotConnectToHost as isize);
pub const NetworkConnectionLost: Self = Self(Cases::NetworkConnectionLost as isize);
pub const DNSLookupFailed: Self = Self(Cases::DNSLookupFailed as isize);
pub const HTTPTooManyRedirects: Self = Self(Cases::HTTPTooManyRedirects as isize);
pub const ResourceUnavailable: Self = Self(Cases::ResourceUnavailable as isize);
pub const NotConnectedToInternet: Self = Self(Cases::NotConnectedToInternet as isize);
pub const RedirectToNonExistentLocation: Self =
Self(Cases::RedirectToNonExistentLocation as isize);
pub const BadServerResponse: Self = Self(Cases::BadServerResponse as isize);
pub const UserCancelledAuthentication: Self =
Self(Cases::UserCancelledAuthentication as isize);
pub const UserAuthenticationRequired: Self =
Self(Cases::UserAuthenticationRequired as isize);
pub const ZeroByteResource: Self = Self(Cases::ZeroByteResource as isize);
pub const CannotDecodeRawData: Self = Self(Cases::CannotDecodeRawData as isize);
pub const CannotDecodeContentData: Self = Self(Cases::CannotDecodeContentData as isize);
pub const CannotParseResponse: Self = Self(Cases::CannotParseResponse as isize);
pub const AppTransportSecurityRequiresSecureConnection: Self =
Self(Cases::AppTransportSecurityRequiresSecureConnection as isize);
pub const FileDoesNotExist: Self = Self(Cases::FileDoesNotExist as isize);
pub const FileIsDirectory: Self = Self(Cases::FileIsDirectory as isize);
pub const NoPermissionsToReadFile: Self = Self(Cases::NoPermissionsToReadFile as isize);
pub const DataLengthExceedsMaximum: Self = Self(Cases::DataLengthExceedsMaximum as isize);
pub const FileOutsideSafeArea: Self = Self(Cases::FileOutsideSafeArea as isize);
pub const SecureConnectionFailed: Self = Self(Cases::SecureConnectionFailed as isize);
pub const ServerCertificateHasBadDate: Self =
Self(Cases::ServerCertificateHasBadDate as isize);
pub const ServerCertificateUntrusted: Self =
Self(Cases::ServerCertificateUntrusted as isize);
pub const ServerCertificateHasUnknownRoot: Self =
Self(Cases::ServerCertificateHasUnknownRoot as isize);
pub const ServerCertificateNotYetValid: Self =
Self(Cases::ServerCertificateNotYetValid as isize);
pub const ClientCertificateRejected: Self = Self(Cases::ClientCertificateRejected as isize);
pub const ClientCertificateRequired: Self = Self(Cases::ClientCertificateRequired as isize);
pub const CannotLoadFromNetwork: Self = Self(Cases::CannotLoadFromNetwork as isize);
pub const CannotCreateFile: Self = Self(Cases::CannotCreateFile as isize);
pub const CannotOpenFile: Self = Self(Cases::CannotOpenFile as isize);
pub const CannotCloseFile: Self = Self(Cases::CannotCloseFile as isize);
pub const CannotWriteToFile: Self = Self(Cases::CannotWriteToFile as isize);
pub const CannotRemoveFile: Self = Self(Cases::CannotRemoveFile as isize);
pub const CannotMoveFile: Self = Self(Cases::CannotMoveFile as isize);
pub const DownloadDecodingFailedMidStream: Self =
Self(Cases::DownloadDecodingFailedMidStream as isize);
pub const DownloadDecodingFailedToComplete: Self =
Self(Cases::DownloadDecodingFailedToComplete as isize);
pub const InternationalRoamingOff: Self = Self(Cases::InternationalRoamingOff as isize);
pub const CallIsActive: Self = Self(Cases::CallIsActive as isize);
pub const DataNotAllowed: Self = Self(Cases::DataNotAllowed as isize);
pub const RequestBodyStreamExhausted: Self =
Self(Cases::RequestBodyStreamExhausted as isize);
pub const BackgroundSessionRequiresSharedContainer: Self =
Self(Cases::BackgroundSessionRequiresSharedContainer as isize);
pub const BackgroundSessionInUseByAnotherProcess: Self =
Self(Cases::BackgroundSessionInUseByAnotherProcess as isize);
pub const BackgroundSessionWasDisconnected: Self =
Self(Cases::BackgroundSessionWasDisconnected as isize);
pub fn cases(&self) -> core::option::Option<Cases> {
match self {
&Self::Unknown => Cases::Unknown.into(),
&Self::Cancelled => Cases::Cancelled.into(),
&Self::BadURL => Cases::BadURL.into(),
&Self::TimedOut => Cases::TimedOut.into(),
&Self::UnsupportedURL => Cases::UnsupportedURL.into(),
&Self::CannotFindHost => Cases::CannotFindHost.into(),
&Self::CannotConnectToHost => Cases::CannotConnectToHost.into(),
&Self::NetworkConnectionLost => Cases::NetworkConnectionLost.into(),
&Self::DNSLookupFailed => Cases::DNSLookupFailed.into(),
&Self::HTTPTooManyRedirects => Cases::HTTPTooManyRedirects.into(),
&Self::ResourceUnavailable => Cases::ResourceUnavailable.into(),
&Self::NotConnectedToInternet => Cases::NotConnectedToInternet.into(),
&Self::RedirectToNonExistentLocation => Cases::RedirectToNonExistentLocation.into(),
&Self::BadServerResponse => Cases::BadServerResponse.into(),
&Self::UserCancelledAuthentication => Cases::UserCancelledAuthentication.into(),
&Self::UserAuthenticationRequired => Cases::UserAuthenticationRequired.into(),
&Self::ZeroByteResource => Cases::ZeroByteResource.into(),
&Self::CannotDecodeRawData => Cases::CannotDecodeRawData.into(),
&Self::CannotDecodeContentData => Cases::CannotDecodeContentData.into(),
&Self::CannotParseResponse => Cases::CannotParseResponse.into(),
&Self::AppTransportSecurityRequiresSecureConnection => {
Cases::AppTransportSecurityRequiresSecureConnection.into()
}
&Self::FileDoesNotExist => Cases::FileDoesNotExist.into(),
&Self::FileIsDirectory => Cases::FileIsDirectory.into(),
&Self::NoPermissionsToReadFile => Cases::NoPermissionsToReadFile.into(),
&Self::DataLengthExceedsMaximum => Cases::DataLengthExceedsMaximum.into(),
&Self::FileOutsideSafeArea => Cases::FileOutsideSafeArea.into(),
&Self::SecureConnectionFailed => Cases::SecureConnectionFailed.into(),
&Self::ServerCertificateHasBadDate => Cases::ServerCertificateHasBadDate.into(),
&Self::ServerCertificateUntrusted => Cases::ServerCertificateUntrusted.into(),
&Self::ServerCertificateHasUnknownRoot => {
Cases::ServerCertificateHasUnknownRoot.into()
}
&Self::ServerCertificateNotYetValid => Cases::ServerCertificateNotYetValid.into(),
&Self::ClientCertificateRejected => Cases::ClientCertificateRejected.into(),
&Self::ClientCertificateRequired => Cases::ClientCertificateRequired.into(),
&Self::CannotLoadFromNetwork => Cases::CannotLoadFromNetwork.into(),
&Self::CannotCreateFile => Cases::CannotCreateFile.into(),
&Self::CannotOpenFile => Cases::CannotOpenFile.into(),
&Self::CannotCloseFile => Cases::CannotCloseFile.into(),
&Self::CannotWriteToFile => Cases::CannotWriteToFile.into(),
&Self::CannotRemoveFile => Cases::CannotRemoveFile.into(),
&Self::CannotMoveFile => Cases::CannotMoveFile.into(),
&Self::DownloadDecodingFailedMidStream => {
Cases::DownloadDecodingFailedMidStream.into()
}
&Self::DownloadDecodingFailedToComplete => {
Cases::DownloadDecodingFailedToComplete.into()
}
&Self::InternationalRoamingOff => Cases::InternationalRoamingOff.into(),
&Self::CallIsActive => Cases::CallIsActive.into(),
&Self::DataNotAllowed => Cases::DataNotAllowed.into(),
&Self::RequestBodyStreamExhausted => Cases::RequestBodyStreamExhausted.into(),
&Self::BackgroundSessionRequiresSharedContainer => {
Cases::BackgroundSessionRequiresSharedContainer.into()
}
&Self::BackgroundSessionInUseByAnotherProcess => {
Cases::BackgroundSessionInUseByAnotherProcess.into()
}
&Self::BackgroundSessionWasDisconnected => {
Cases::BackgroundSessionWasDisconnected.into()
}
_ => None,
}
}
#[inline]
pub fn peek(&self) -> &isize {
&self.0
}
#[inline]
pub fn take(self) -> isize {
self.0
}
}
impl From<Cases> for Codes {
fn from(case: Cases) -> Self {
match case {
Cases::Unknown => Codes::Unknown,
Cases::Cancelled => Codes::Cancelled,
Cases::BadURL => Codes::BadURL,
Cases::TimedOut => Codes::TimedOut,
Cases::UnsupportedURL => Codes::UnsupportedURL,
Cases::CannotFindHost => Codes::CannotFindHost,
Cases::CannotConnectToHost => Codes::CannotConnectToHost,
Cases::NetworkConnectionLost => Codes::NetworkConnectionLost,
Cases::DNSLookupFailed => Codes::DNSLookupFailed,
Cases::HTTPTooManyRedirects => Codes::HTTPTooManyRedirects,
Cases::ResourceUnavailable => Codes::ResourceUnavailable,
Cases::NotConnectedToInternet => Codes::NotConnectedToInternet,
Cases::RedirectToNonExistentLocation => Codes::RedirectToNonExistentLocation,
Cases::BadServerResponse => Codes::BadServerResponse,
Cases::UserCancelledAuthentication => Codes::UserCancelledAuthentication,
Cases::UserAuthenticationRequired => Codes::UserAuthenticationRequired,
Cases::ZeroByteResource => Codes::ZeroByteResource,
Cases::CannotDecodeRawData => Codes::CannotDecodeRawData,
Cases::CannotDecodeContentData => Codes::CannotDecodeContentData,
Cases::CannotParseResponse => Codes::CannotParseResponse,
Cases::AppTransportSecurityRequiresSecureConnection => {
Codes::AppTransportSecurityRequiresSecureConnection
}
Cases::FileDoesNotExist => Codes::FileDoesNotExist,
Cases::FileIsDirectory => Codes::FileIsDirectory,
Cases::NoPermissionsToReadFile => Codes::NoPermissionsToReadFile,
Cases::DataLengthExceedsMaximum => Codes::DataLengthExceedsMaximum,
Cases::FileOutsideSafeArea => Codes::FileOutsideSafeArea,
Cases::SecureConnectionFailed => Codes::SecureConnectionFailed,
Cases::ServerCertificateHasBadDate => Codes::ServerCertificateHasBadDate,
Cases::ServerCertificateUntrusted => Codes::ServerCertificateUntrusted,
Cases::ServerCertificateHasUnknownRoot => Codes::ServerCertificateHasUnknownRoot,
Cases::ServerCertificateNotYetValid => Codes::ServerCertificateNotYetValid,
Cases::ClientCertificateRejected => Codes::ClientCertificateRejected,
Cases::ClientCertificateRequired => Codes::ClientCertificateRequired,
Cases::CannotLoadFromNetwork => Codes::CannotLoadFromNetwork,
Cases::CannotCreateFile => Codes::CannotCreateFile,
Cases::CannotOpenFile => Codes::CannotOpenFile,
Cases::CannotCloseFile => Codes::CannotCloseFile,
Cases::CannotWriteToFile => Codes::CannotWriteToFile,
Cases::CannotRemoveFile => Codes::CannotRemoveFile,
Cases::CannotMoveFile => Codes::CannotMoveFile,
Cases::DownloadDecodingFailedMidStream => Codes::DownloadDecodingFailedMidStream,
Cases::DownloadDecodingFailedToComplete => Codes::DownloadDecodingFailedToComplete,
Cases::InternationalRoamingOff => Codes::InternationalRoamingOff,
Cases::CallIsActive => Codes::CallIsActive,
Cases::DataNotAllowed => Codes::DataNotAllowed,
Cases::RequestBodyStreamExhausted => Codes::RequestBodyStreamExhausted,
Cases::BackgroundSessionRequiresSharedContainer => {
Codes::BackgroundSessionRequiresSharedContainer
}
Cases::BackgroundSessionInUseByAnotherProcess => {
Codes::BackgroundSessionInUseByAnotherProcess
}
Cases::BackgroundSessionWasDisconnected => Codes::BackgroundSessionWasDisconnected,
}
}
}
impl core::convert::From<Codes> for isize {
#[inline]
fn from(wrapper: Codes) -> Self {
wrapper.take()
}
}
impl From<Cases> for isize {
#[inline]
fn from(case: Cases) -> Self {
Self::from(Codes::from(case))
}
}
impl objc2::TypedEnum for Codes {
type Cases = Cases;
}
#[cfg(all(feature = "Foundation", feature = "Foundation_NSNumber"))]
impl From<Codes> for objc2::rc::Id<icrate::Foundation::NSNumber> {
#[inline]
fn from(value: Codes) -> Self {
icrate::Foundation::NSNumber::new_isize(value.0.into())
}
}
#[cfg(feature = "Foundation")]
impl core::ops::Deref for Codes {
type Target = icrate::Foundation::NSObject;
fn deref(&self) -> &Self::Target {
let num = icrate::Foundation::NSNumber::new_isize(self.0.into());
let obj = unsafe { objc2::rc::Id::cast::<icrate::Foundation::NSObject>(num) };
let obj = objc2::rc::Id::autorelease_return(obj);
let obj = unsafe { obj.as_ref() }.expect("pointer is non-null");
obj
}
}
#[cfg(all(feature = "Foundation", feature = "Foundation_NSNumber"))]
impl TryFrom<&icrate::Foundation::NSNumber> for Codes {
type Error = &'static str;
fn try_from(number: &icrate::Foundation::NSNumber) -> Result<Self, Self::Error> {
use objc2::Encoding::*;
let encoding = number.encoding();
let equivalent = match "isize" {
"usize" => {
encoding == ULong
|| (cfg!(target_pointer_width = "32") && encoding == UInt)
|| (cfg!(target_pointer_width = "64") && encoding == ULongLong)
}
"isize" => {
encoding == Long
|| (cfg!(target_pointer_width = "32") && encoding == Int)
|| (cfg!(target_pointer_width = "64") && encoding == LongLong)
}
"u8" => encoding == UChar,
"u16" => encoding == UShort,
"u32" => {
encoding == UInt || (cfg!(target_pointer_width = "32") && encoding == ULong)
}
"u64" => {
encoding == ULongLong
|| (cfg!(target_pointer_width = "64") && encoding == ULong)
}
"i8" => encoding == Char,
"i16" => encoding == Short,
"i32" => encoding == Int || (cfg!(target_pointer_width = "32") && encoding == Long),
"i64" => {
encoding == LongLong || (cfg!(target_pointer_width = "64") && encoding == Long)
}
_ => false,
};
if equivalent {
let number = number.as_isize();
let value = Self(number);
if value.cases().is_some() {
Ok(value)
} else {
Err("NSNumber value does not match any of the enum values")
}
} else {
Err("NSNumber encoding is not equivalent to error repr")
}
}
}
impl<O: objc2::rc::Ownership> TryFrom<objc2::rc::Id<icrate::Foundation::NSNumber, O>> for Codes {
type Error = &'static str;
#[inline]
fn try_from(number: Id<icrate::Foundation::NSNumber, O>) -> Result<Self, Self::Error> {
(&*number).try_into()
}
}
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum Cases {
Unknown = -1,
Cancelled = -999,
BadURL = -1000,
TimedOut = -1001,
UnsupportedURL = -1002,
CannotFindHost = -1003,
CannotConnectToHost = -1004,
NetworkConnectionLost = -1005,
DNSLookupFailed = -1006,
HTTPTooManyRedirects = -1007,
ResourceUnavailable = -1008,
NotConnectedToInternet = -1009,
RedirectToNonExistentLocation = -1010,
BadServerResponse = -1011,
UserCancelledAuthentication = -1012,
UserAuthenticationRequired = -1013,
ZeroByteResource = -1014,
CannotDecodeRawData = -1015,
CannotDecodeContentData = -1016,
CannotParseResponse = -1017,
AppTransportSecurityRequiresSecureConnection = -1022,
FileDoesNotExist = -1100,
FileIsDirectory = -1101,
NoPermissionsToReadFile = -1102,
DataLengthExceedsMaximum = -1103,
FileOutsideSafeArea = -1104,
SecureConnectionFailed = -1200,
ServerCertificateHasBadDate = -1201,
ServerCertificateUntrusted = -1202,
ServerCertificateHasUnknownRoot = -1203,
ServerCertificateNotYetValid = -1204,
ClientCertificateRejected = -1205,
ClientCertificateRequired = -1206,
CannotLoadFromNetwork = -2000,
CannotCreateFile = -3000,
CannotOpenFile = -3001,
CannotCloseFile = -3002,
CannotWriteToFile = -3003,
CannotRemoveFile = -3004,
CannotMoveFile = -3005,
DownloadDecodingFailedMidStream = -3006,
DownloadDecodingFailedToComplete = -3007,
InternationalRoamingOff = -1018,
CallIsActive = -1019,
DataNotAllowed = -1020,
RequestBodyStreamExhausted = -1021,
BackgroundSessionRequiresSharedContainer = -995,
BackgroundSessionInUseByAnotherProcess = -996,
BackgroundSessionWasDisconnected = -997,
}
#[allow(unused_imports)]
use super::*;
#[cfg(feature = "Foundation")]
#[allow(unused_imports)]
use icrate::Foundation::*;
#[allow(unused_imports)]
use objc2::{rc::Id, runtime::Object};
#[derive(Default)]
pub struct UserInfo {
pub failing_url: Option<Id<NSURL>>,
pub failing_url_string: Option<Id<NSString>>,
pub background_task_cancelled_reason:
Option<ns_url_error_reasons::BackgroundTaskCancelledReason>,
pub network_unavailable_reason: Option<ns_url_error_reasons::NetworkUnavailableReason>,
}
} |
I think it's important to separate syntax of the macro from the desired output. To this end, I've edited the top-level comment with what I think the desired output should be, then that part is perhaps easier to discuss. I can see you've spent a lot of time on translation of error enums - I'm quite uncertain about those myself, but I think you've taken a big step in the right direction. Comments on that:
Regarding macro syntax, I think it's basically irrelevant since I don't think it'll really be necessary to expose the error enum macro to users? In any case, I think I'd like to split the error and the |
Without the That's the only reason they are there really. We could easily make them optional as well, for cases where they wouldn't be needed.
I mostly followed the design and rationale that was laid out here. Personally, I think we really should have something like this if the intention is truly to allow Rust (through this crate ecosystem) to be an alternative to Swift in terms of ergonomics and feature parity (as far as that is possible). Did you have another alternative in mind perhaps?
The main motivation for The experience of just pressing a key combination and getting all of the error cases filled in versus looking them up individually in the docs (or even just completing each one manually) makes a fairly big difference in usability from my perspective.
We have both actually. I generate individual getters first, then the code that fetches the entire dictionary is just implemented in terms of that: pub fn user_info(&self) -> ns_url_error::UserInfo {
let mut user_info = ns_url_error::UserInfo::default();
user_info.failing_url = self.failing_url();
user_info.failing_url_string = self.failing_url_string();
user_info.background_task_cancelled_reason = self.background_task_cancelled_reason();
user_info.network_unavailable_reason = self.network_unavailable_reason();
user_info
}
I think that would probably be fine. Part of the rationale behind the use of the submodules was to allow for fewer imports (by relying on the
I'm not sure actually. Do people use
It would be possible to split the The way the code is implemented, they are not deeply intertwined. Mostly the |
Thanks for the examples. That gives a good point of reference. I'll comment on the individual samples versus what is currently implemented.
I am currently using the With regard to whether we want all the additional helper traits from I think the maintenance burden of having to maintain our own reimplementation is probably not worth it. But that's just my personal take.
I am currently translating this as an enum, but was worried about the exact issue you mention (which I hadn't tested yet). I think the best option is just to handle this case the same as
I am currently translating this how you suggest but haven't added the
The representation you propose for both of these is different to how I am currently generating them. For starters I am using The other part that is different is that I am (or was) using So I can change that part. But that also brings me back to the issue with pattern matching, and why I think helper Generating
I'm not sure there is an important distinction between these cases, other than being an artifact of the conventions used for writing the headers at various points in time. Sometimes the headers are not using the most appropriate definitions or contain other issues. For instance, sometimes they are not using the I think we should be looking at what the Swift analogue is for such cases and use that as a point of reference for our desired result. In that case, MTLIOError and URLError are defined similarly. |
I've started adding some of the stuff you mentioned in your original post that was missing from the macro translation. I've also added a new test file And by the way, how are you thinking of handling the We could make deriving those optional through some additional arguments perhaps. In any case, this is how the
|
Hmm, perhaps I sounded dismissive there? I agree that we should have something like this!
Hmm, I never considered that, I agree that would be quite nice to have! My problems with it is:
I think it would be possible to do something like this: #[non_exhaustive]
enum NSFoo {
A = 1,
B = 2,
#[doc(hidden)]
__Unknown(usize),
}
impl EncodeConvertArgument for NSFoo { ... }
impl EncodeConvertResult for NSFoo { ... } Which would allow taking and returning
Yeah, but that calls the
I haven't planned for
I think they should be separate, mostly because it seems like the error stuff we can autogenerate, while the
No, they act exactly the same for structs with one field - the reason I wrote
Hmm, yeah, performance is a bit less of a concern here, since we're dealing with string comparisons which are comparatively slow anyhow. What you've done seems fairly optimal, though I still dislike that we're emitting two types :/
I think I was mostly thinking of it in terms of what we can autogenerate, and what can't - e.g.
I think it's worth it to special-case |
Great! And by the way, I do agree there are a number of issues with the current implementation. I'm just hoping that there's a good idea in here somewhere that we can figure out eventually.
Okay, I understand what you mean better now. We could instead specialize the That seems like a fairly reasonable compromise I think? (versus tending toward a more complex design)
That is a fair point and I understand the comparison. I'm not entirely sure I agree that it's confusing to the same degree though. Memory management and FFI-related code are difficult due to the complex and very situational semantics involved. The cognitive surface area with regard to API design is also generally much larger. In comparison, the user doesn't have to understand too much about the struct-wrapping-values and enum-representing-match-cases pairing. The interface is limited: there's only a single method (
I'm not sure I would expect the user to try to use Consider the following code snippet: fn generate_event(event: &NSEvent) -> Option<Id<NSEvent, Shared>> {
match event.type().cases() {
Some(NSEventTypeCases::LeftMouseDown) => {
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
type: NSEventType::application_defined(),
...
)
}
Some(NSEventTypeCases::KeyDown) => {
...
}
_ => {
None
}
}
} I suppose there might be a situation where you really want to generate the But the more I thought about it, the less likely it seemed that someone would really want to use that in practice, since any time you get the Maybe there is an example of that you had in mind?
Yeah, this one is something I've had concerns about also. I did consider an alternate design that returned a On the other hand, the user doesn't have to unwrap, in the sense of explicitly writing a separate branch. It's still possible to write this, which isn't much more verbose: match value.cases() {
Some(EnumCases::Var0) => ...,
Some(EnumCases::Var1) => ...,
_ => ...,
} In comparison, this is what it would look like without match value {
_ if value == Enum::var0() => ...,
_ if value == Enum::var1() => ...,
_ => ...,
} This seems more verbose and you also lose the exhaustivity checking and tooling support for filling in the cases. However, despite advocating for For the sake of moving this forward, I could disable (or make opt-in), the
That's a good point and I think you are probably right in general. The one case that I had in mind where it might be relevant beyond this project is for commercial users of the crate. I could certainly imagine a developer with a large amount of code written in Objective-C that might be able to make use of
Not specifically. There are some interesting frameworks like ResearchKit that would be nice to translate but nothing I would probably use for my own projects at this point.
Ah, okay, good to know.
Makes sense. I guess I was imagining that we would probably need to do this already for a lot of the error enums in order to have nice interfaces. But it is important to consider how much of it we could autogenerate for sure. Relatedly:
Okay, that makes sense also. I already started working on providing an alternative interface where that macro is separate so it will probably be in the PR branch soon.
Okay, that's doable. Anyway, thanks for the feedback! You definitely raised a number of good points. |
Update on this:
This means that nowadays, the " fn generate_event(event: &NSEvent) -> Option<Id<NSEvent, Shared>> {
match event.type() {
NSEventType::LeftMouseDown => {
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
type: NSEventType::application_defined(),
...
)
}
NSEventType::KeyDown => {
...
}
_ => {
None
}
}
} Your IDE would not autofill the cases, doing so is still blocked on better niche support in Rust. |
Linking a bit of discussion on non-exhaustive C-like enums on Zulip recently, see this topic. |
icrate
Right now we're just using type aliases - instead, we should generate newtypes with the appropriate traits and constants. See also how Swift does it.
EDIT: I think the output should be something like this (depending on the naming scheme, so until that the variant/method names might be longer):
NS_OPTIONS
(example:NSSortOptions
):NS_ENUM
(example:NSDecodingFailurePolicy
):NS_CLOSED_ENUM
(example:NSComparisonResult
):NS_TYPED_ENUM
(example:NSStringEncodingDetectionOptionsKey
):NS_TYPED_EXTENSIBLE_ENUM
(example:NSExceptionName
):NS_ERROR_ENUM
with typedef (example:MTLIOError
+MTLIOErrorDomain
):NS_ERROR_ENUM
without typedef (example:NSURLErrorDomain
+ related URL errors):Anonymous enum (example: Memory Allocation Options):
The text was updated successfully, but these errors were encountered: